// 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 } }