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:
parent
a9f8cb7e0f
commit
5d4d318ebf
10 changed files with 813 additions and 37 deletions
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue