udev works: /sys device tree, uevent netlink, datagram sockets with per-message credentials

Kernel devices (input, mem, tty, graphics, block) get /sys/devices/virtual
entries with dev/uevent/subsystem, /sys/class and /sys/dev links, input
capabilities, and writable uevent files that announce on the netlink socket.
eudev now runs, processes a full trigger and settles.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
Nexoniarz 2026-10-26 11:39:13 +01:00
commit 5d4d318ebf
10 changed files with 813 additions and 37 deletions

View file

@ -132,43 +132,120 @@ sock_is_packet(struct socket *s)
return is_packet(s);
}
// bind(2) on a netlink socket: only the multicast groups mean anything here.
// Every packet in the ring is an eight-byte header and then the payload: the
// length with the multicast groups it arrived on in the top byte, and the port
// id of whoever sent it. Netlink receivers need both -- a datagram that says
// "no group" is unicast, and is believed only from the one sender they trust.
#define PKTHDR 16
// The multicast groups the datagram last read from this socket was sent to,
// and who sent it.
uint
sock_nl_groups(struct socket *s)
{
return s->domain == AF_NETLINK ? s->nllast : 0;
}
uint
sock_nl_from(struct socket *s)
{
return s->domain == AF_NETLINK ? s->nlfrom : 0;
}
uint
sock_nl_pid(struct socket *s)
{
return s->nlpid;
}
uint
sock_nl_bound_groups(struct socket *s)
{
return s->nlgroups;
}
static uint nlport = 4000;
// bind(2) on a netlink socket. A port id of 0 asks the kernel to choose one,
// which is how a socket becomes something a datagram can be addressed to.
int
sock_netlink_bind(struct socket *s, uint groups)
sock_netlink_bind(struct socket *s, uint pid, uint groups)
{
acquire(&stab.lock);
s->nlgroups = groups;
if(s->nlproto == NETLINK_KOBJECT_UEVENT && s->nlpid == 0)
s->nlpid = pid ? pid : ++nlport;
release(&stab.lock);
return 0;
}
// Queue one uevent datagram on every uevent socket listening to any of
// `groups`, except `from`. A full queue drops the message, as Linux does:
// udev recovers with a trigger, and the sender must never wait on a reader.
void
uevent_send(uint groups, const char *msg, int len, struct socket *from)
// Caller holds stab.lock. Append one packet to dst's ring; 0, or -1 if it
// does not fit.
static int
pkt_put(struct socket *dst, const char *msg, int len, uint groups, uint from,
uint uid, uint gid)
{
if(len < 0 || len + PKTHDR > SOCKBUF ||
dst->nwrite - dst->nread + (uint)len + PKTHDR > SOCKBUF)
return -1;
uint h0 = (uint)len | ((groups & 0xFF) << 24);
for(int i = 0; i < 4; i++)
dst->data[dst->nwrite++ % SOCKBUF] = (char)(h0 >> (8 * i));
for(int i = 0; i < 4; i++)
dst->data[dst->nwrite++ % SOCKBUF] = (char)(from >> (8 * i));
for(int i = 0; i < 4; i++)
dst->data[dst->nwrite++ % SOCKBUF] = (char)(uid >> (8 * i));
for(int i = 0; i < 4; i++)
dst->data[dst->nwrite++ % SOCKBUF] = (char)(gid >> (8 * i));
for(int i = 0; i < len; i++)
dst->data[dst->nwrite++ % SOCKBUF] = msg[i];
return 0;
}
// Queue one uevent datagram: on the socket whose port id is `dpid` if there is
// one, and on every uevent socket bound to any of `groups`, except `from`. A
// full queue drops the message, as Linux does: udev recovers with a trigger,
// and the sender must never wait on a reader.
static void
uevent_dispatch(uint groups, uint dpid, const char *msg, int len, struct socket *from)
{
struct socket *s;
uint spid = from ? from->nlpid : 0;
if(len <= 0 || len + 4 > SOCKBUF)
if(len <= 0)
return;
acquire(&stab.lock);
for(s = stab.sock; s < stab.sock + NSOCK; s++){
if(s->state == SS_FREE || s == from || s->domain != AF_NETLINK ||
s->nlproto != NETLINK_KOBJECT_UEVENT || !(s->nlgroups & groups))
s->nlproto != NETLINK_KOBJECT_UEVENT)
continue;
if(s->nwrite - s->nread + (uint)len + 4 > SOCKBUF)
uint g = groups & s->nlgroups;
if(dpid && s->nlpid == dpid)
g = 0; // unicast: no group, so the reader checks the sender
else if(!g)
continue;
for(int i = 0; i < 4; i++)
s->data[s->nwrite++ % SOCKBUF] = (char)((uint)len >> (8 * i));
for(int i = 0; i < len; i++)
s->data[s->nwrite++ % SOCKBUF] = msg[i];
wakeup(&s->nread);
if(pkt_put(s, msg, len, g, spid, 0, 0) == 0)
wakeup(&s->nread);
}
release(&stab.lock);
poll_kick();
}
void
uevent_send(uint groups, const char *msg, int len, struct socket *from)
{
uevent_dispatch(groups, 0, msg, len, from);
}
int
sock_uevent_send(struct socket *s, const char *msg, int len, uint dpid, uint dgroups)
{
if(len + PKTHDR > SOCKBUF)
return -EMSGSIZE;
uevent_dispatch(dgroups, dpid, msg, len, s);
return len;
}
// Caller holds stab.lock.
static struct socket *
alloc_locked(int type)
@ -486,8 +563,10 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
return n;
}
// A datagram socket has no end of stream: a reader whose peer has gone just
// waits, as on Linux, instead of reading "zero bytes" for ever.
acquire(&stab.lock);
while(s->nread == s->nwrite && !s->peer_closed){
while(s->nread == s->nwrite && !(s->peer_closed && s->type != SOCK_DGRAM)){
if(myproc()->killed || s->state == SS_FREE){
release(&stab.lock);
return -EINTR;
@ -497,9 +576,15 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
// One message, whole or truncated; descriptors ride with the header.
if(is_packet(s) && s->nread != s->nwrite){
uint len = 0;
uint len = 0, from = 0, puid = 0, pgid = 0;
for(i = 0; i < 4; i++)
len |= (uint)(uchar)s->data[(s->nread + i) % SOCKBUF] << (8 * i);
for(i = 0; i < 4; i++)
from |= (uint)(uchar)s->data[(s->nread + 4 + i) % SOCKBUF] << (8 * i);
for(i = 0; i < 4; i++)
puid |= (uint)(uchar)s->data[(s->nread + 8 + i) % SOCKBUF] << (8 * i);
for(i = 0; i < 4; i++)
pgid |= (uint)(uchar)s->data[(s->nread + 12 + i) % SOCKBUF] << (8 * i);
if(s->nscm > 0 && s->scm[0].pos == s->nread){
got = s->scm[0].n;
for(i = 0; i < got; i++)
@ -508,7 +593,13 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
s->scm[i - 1] = s->scm[i];
s->nscm--;
}
s->nread += 4;
s->nread += PKTHDR;
s->nllast = len >> 24;
s->nlfrom = from;
s->pk_uid = puid;
s->pk_gid = pgid;
s->pk_valid = 1;
len &= 0xFFFFFF;
uint take = len < (uint)n ? len : (uint)n;
for(i = 0; i < (int)take; i++)
addr[i] = s->data[(s->nread + i) % SOCKBUF];
@ -610,7 +701,7 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
// MSG_DONTWAIT and treats a short read as the end of the dump.
if(s->domain == AF_NETLINK && is_packet(s)){
// udevd announces what it did on group 2; the sender does not hear itself.
uevent_send(UEVENT_GROUP_UDEV, addr, n, s);
uevent_dispatch(UEVENT_GROUP_UDEV, 0, addr, n, s);
return n;
}
if(s->domain == AF_NETLINK){
@ -643,7 +734,7 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
}
if(is_packet(s)){
if(n + 4 > SOCKBUF){
if(n + PKTHDR > SOCKBUF){
release(&stab.lock);
return -EMSGSIZE;
}
@ -654,7 +745,7 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
sigsend(myproc()->pid, SIGPIPE);
return -EPIPE;
}
if(dst->nwrite - dst->nread + (uint)n + 4 <= SOCKBUF)
if(dst->nwrite - dst->nread + (uint)n + PKTHDR <= SOCKBUF)
break;
if(myproc()->killed){
release(&stab.lock);
@ -674,10 +765,11 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
for(int j = 0; j < nf; j++)
b->f[j] = f[j];
}
for(i = 0; i < 4; i++)
dst->data[dst->nwrite++ % SOCKBUF] = (char)((uint)n >> (8 * i));
for(i = 0; i < n; i++)
dst->data[dst->nwrite++ % SOCKBUF] = addr[i];
// The sender's credentials travel with the datagram, not with the socket:
// several processes can share one end (udev's workers do) and the reader
// has to know which of them each message came from.
pkt_put(dst, addr, n, 0, myproc() ? (uint)myproc()->pid : 0,
myproc() ? (uint)myproc()->euid : 0, myproc() ? (uint)myproc()->egid : 0);
wakeup(&dst->nread);
release(&stab.lock);
poll_kick();
@ -1118,7 +1210,7 @@ sockreadable(struct socket *s)
return inet_readable(s->inet);
if(s->state == SS_LISTENING)
return s->nqueue > 0;
return s->nread != s->nwrite || s->peer_closed;
return s->nread != s->nwrite || (s->peer_closed && s->type != SOCK_DGRAM);
}
// The peer is gone and everything it sent has been read.
@ -1237,6 +1329,19 @@ sock_sendercred(struct socket *s, int *pid, uint *uid, uint *gid)
{
int r = -1;
// Uevents come from the kernel -- or from udevd re-announcing them -- and a
// receiver that asked for credentials discards a datagram without them.
if(s->domain == AF_NETLINK && s->nlproto == NETLINK_KOBJECT_UEVENT){
*pid = 0; *uid = 0; *gid = 0;
return 0;
}
// A packet socket reports whoever sent the datagram just read.
if(s->domain == AF_UNIX && is_packet(s) && s->pk_valid){
*pid = (int)s->nlfrom; *uid = s->pk_uid; *gid = s->pk_gid;
return 0;
}
acquire(&stab.lock);
if(s->domain != AF_INET && s->peer){
struct socket *p = s->peer;