All checks were successful
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m14s
Test / ShareFS (push) Successful in 4m18s
Test / Hakurei (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 5m37s
Test / Hakurei (race detector) (push) Successful in 6m39s
Test / Flake checks (push) Successful in 1m24s
These are not possible to cover outside integration vm. Extreme care is required when dealing with this method, so keep it simple. Signed-off-by: Ophestra <cat@gensokyo.uk>
106 lines
2.7 KiB
Go
106 lines
2.7 KiB
Go
// Package uevent provides userspace client for consuming events from a
|
|
// NETLINK_KOBJECT_UEVENT socket, as well as helpers for supplementing
|
|
// events received from the kernel.
|
|
package uevent
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strconv"
|
|
"sync/atomic"
|
|
"syscall"
|
|
|
|
"hakurei.app/internal/netlink"
|
|
)
|
|
|
|
type (
|
|
// Recoverable is satisfied by errors that are safe to recover from.
|
|
Recoverable interface{ recoverable() }
|
|
// Nontrivial is satisfied by errors preferring a JSON encoding.
|
|
Nontrivial interface{ nontrivial() }
|
|
)
|
|
|
|
// Conn represents a NETLINK_KOBJECT_UEVENT socket.
|
|
type Conn struct {
|
|
conn *netlink.Conn
|
|
|
|
// Whether currently between a call to enterExcl and exitExcl.
|
|
excl atomic.Bool
|
|
}
|
|
|
|
// enterExcl must be called entering a critical section that interacts with conn.
|
|
func (c *Conn) enterExcl() error {
|
|
if !c.excl.CompareAndSwap(false, true) {
|
|
return syscall.EAGAIN
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// exitExcl must be called exiting a critical section that interacts with conn.
|
|
func (c *Conn) exitExcl() { c.excl.Store(false) }
|
|
|
|
// Close closes the underlying socket.
|
|
func (c *Conn) Close() error { return c.conn.Close() }
|
|
|
|
// Dial returns the address of a newly connected [Conn].
|
|
func Dial() (*Conn, error) {
|
|
// kernel group is hard coded in lib/kobject_uevent.c, undocumented
|
|
c, err := netlink.Dial(syscall.NETLINK_KOBJECT_UEVENT, 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Conn{conn: c}, err
|
|
}
|
|
|
|
var (
|
|
// ErrBadSocket is returned by [Conn.Consume] for a reply from a
|
|
// syscall.Sockaddr with unexpected concrete type.
|
|
ErrBadSocket = errors.New("unexpected socket address")
|
|
)
|
|
|
|
// BadPortError is returned by [Conn.Consume] upon receiving a message that did
|
|
// not come from the kernel.
|
|
type BadPortError syscall.SockaddrNetlink
|
|
|
|
var _ Recoverable = new(BadPortError)
|
|
|
|
func (*BadPortError) recoverable() {}
|
|
func (e *BadPortError) Error() string {
|
|
return "unexpected message from port id " + strconv.Itoa(int(e.Pid)) +
|
|
" on NETLINK_KOBJECT_UEVENT"
|
|
}
|
|
|
|
// Consume continuously receives and parses events from the kernel. It returns
|
|
// the first error it encounters.
|
|
//
|
|
// Callers must not restart event processing after a non-nil error that does not
|
|
// satisfy [Recoverable] is returned.
|
|
func (c *Conn) Consume(ctx context.Context, events chan<- *Message) error {
|
|
if err := c.enterExcl(); err != nil {
|
|
return err
|
|
}
|
|
defer c.exitExcl()
|
|
|
|
for {
|
|
data, from, err := c.conn.Recvfrom(ctx, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// lib/kobject_uevent.c:
|
|
// set portid 0 to inform userspace message comes from kernel
|
|
if v, ok := from.(*syscall.SockaddrNetlink); !ok {
|
|
return ErrBadSocket
|
|
} else if v.Pid != 0 {
|
|
return (*BadPortError)(v)
|
|
|
|
}
|
|
|
|
var msg Message
|
|
if err = msg.UnmarshalBinary(data); err != nil {
|
|
return err
|
|
}
|
|
events <- &msg
|
|
}
|
|
}
|