AF_UNIX SEQPACKET/DGRAM, writable /dev overlay, uevent netlink socket; eudev packages
udevd now starts, reads its rules and listens. Still to come: the sysfs device tree and writable uevent files so a trigger has something to replay. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
parent
ed81f9a07a
commit
a9f8cb7e0f
7 changed files with 457 additions and 40 deletions
|
|
@ -113,6 +113,62 @@ sockinit(void)
|
|||
"the socket table, with buffered bytes and descriptors in flight");
|
||||
}
|
||||
|
||||
// SOCK_SEQPACKET and SOCK_DGRAM keep message boundaries. They ride in the same
|
||||
// ring as a stream, each message behind a four-byte length, so a write is one
|
||||
// message and a read takes exactly one -- the rest of a message that does not
|
||||
// fit the buffer is dropped, as Linux drops it. A message is never split, so
|
||||
// one has to fit the ring whole.
|
||||
static int
|
||||
is_packet(struct socket *s)
|
||||
{
|
||||
if(s->domain == AF_NETLINK)
|
||||
return s->nlproto == NETLINK_KOBJECT_UEVENT;
|
||||
return s->domain == AF_UNIX && (s->type == SOCK_DGRAM || s->type == SOCK_SEQPACKET);
|
||||
}
|
||||
|
||||
int
|
||||
sock_is_packet(struct socket *s)
|
||||
{
|
||||
return is_packet(s);
|
||||
}
|
||||
|
||||
// bind(2) on a netlink socket: only the multicast groups mean anything here.
|
||||
int
|
||||
sock_netlink_bind(struct socket *s, uint groups)
|
||||
{
|
||||
acquire(&stab.lock);
|
||||
s->nlgroups = groups;
|
||||
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)
|
||||
{
|
||||
struct socket *s;
|
||||
|
||||
if(len <= 0 || len + 4 > SOCKBUF)
|
||||
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))
|
||||
continue;
|
||||
if(s->nwrite - s->nread + (uint)len + 4 > SOCKBUF)
|
||||
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);
|
||||
}
|
||||
release(&stab.lock);
|
||||
poll_kick();
|
||||
}
|
||||
|
||||
// Caller holds stab.lock.
|
||||
static struct socket *
|
||||
alloc_locked(int type)
|
||||
|
|
@ -415,7 +471,7 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
|
|||
// so an empty buffer means the caller has already drained this dump rather
|
||||
// than that more is coming. Blocking here would hang a getifaddrs() that
|
||||
// asked one question too many.
|
||||
if(s->domain == AF_NETLINK){
|
||||
if(s->domain == AF_NETLINK && !is_packet(s)){
|
||||
acquire(&stab.lock);
|
||||
uint have = s->nwrite - s->nread;
|
||||
if(have == 0){
|
||||
|
|
@ -439,6 +495,39 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
|
|||
sleep(&s->nread, &stab.lock);
|
||||
}
|
||||
|
||||
// One message, whole or truncated; descriptors ride with the header.
|
||||
if(is_packet(s) && s->nread != s->nwrite){
|
||||
uint len = 0;
|
||||
for(i = 0; i < 4; i++)
|
||||
len |= (uint)(uchar)s->data[(s->nread + 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++)
|
||||
taken[i] = s->scm[0].f[i];
|
||||
for(i = 1; i < s->nscm; i++)
|
||||
s->scm[i - 1] = s->scm[i];
|
||||
s->nscm--;
|
||||
}
|
||||
s->nread += 4;
|
||||
uint take = len < (uint)n ? len : (uint)n;
|
||||
for(i = 0; i < (int)take; i++)
|
||||
addr[i] = s->data[(s->nread + i) % SOCKBUF];
|
||||
s->nread += len;
|
||||
wakeup(&s->nwrite);
|
||||
release(&stab.lock);
|
||||
poll_kick();
|
||||
if(fout){
|
||||
for(int j = 0; j < got; j++)
|
||||
fout[j] = taken[j];
|
||||
if(nfout)
|
||||
*nfout = got;
|
||||
} else {
|
||||
for(int j = 0; j < got; j++)
|
||||
fileclose(taken[j]);
|
||||
}
|
||||
return (int)take;
|
||||
}
|
||||
|
||||
// A message carrying descriptors is a boundary. This read either starts
|
||||
// exactly on one -- and takes them -- or is cut short so that it stops
|
||||
// before it, leaving the next read to start there.
|
||||
|
|
@ -519,6 +608,11 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
|
|||
// rather than a peer's -- there is no peer. Doing the work here is what lets
|
||||
// the read that follows never block, which matters because musl reads with
|
||||
// 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);
|
||||
return n;
|
||||
}
|
||||
if(s->domain == AF_NETLINK){
|
||||
acquire(&stab.lock);
|
||||
s->nread = s->nwrite = 0;
|
||||
|
|
@ -548,6 +642,48 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
|
|||
return -ENOBUFS;
|
||||
}
|
||||
|
||||
if(is_packet(s)){
|
||||
if(n + 4 > SOCKBUF){
|
||||
release(&stab.lock);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
for(;;){
|
||||
dst = s->peer;
|
||||
if(dst == 0){
|
||||
release(&stab.lock);
|
||||
sigsend(myproc()->pid, SIGPIPE);
|
||||
return -EPIPE;
|
||||
}
|
||||
if(dst->nwrite - dst->nread + (uint)n + 4 <= SOCKBUF)
|
||||
break;
|
||||
if(myproc()->killed){
|
||||
release(&stab.lock);
|
||||
return -EINTR;
|
||||
}
|
||||
wakeup(&dst->nread);
|
||||
sleep(&dst->nwrite, &stab.lock);
|
||||
}
|
||||
if(nf > 0){
|
||||
if(dst->nscm >= NSCM){
|
||||
release(&stab.lock);
|
||||
return -ENOBUFS;
|
||||
}
|
||||
struct scm_fds *b = &dst->scm[dst->nscm++];
|
||||
b->pos = dst->nwrite; // the header, where a packet read starts
|
||||
b->n = 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];
|
||||
wakeup(&dst->nread);
|
||||
release(&stab.lock);
|
||||
poll_kick();
|
||||
return n;
|
||||
}
|
||||
|
||||
for(i = 0; i < n; i++){
|
||||
// Re-read every iteration: the peer can go away while we sleep below.
|
||||
dst = s->peer;
|
||||
|
|
@ -607,9 +743,11 @@ socknew(int domain, int type, int proto)
|
|||
|
||||
if(domain != AF_UNIX && domain != AF_INET && domain != AF_NETLINK)
|
||||
return 0;
|
||||
// AF_UNIX is stream-only here; AF_INET has datagrams too, and inet_new is
|
||||
// AF_UNIX: streams, and message-preserving SEQPACKET and DGRAM (connected
|
||||
// pairs and listeners; an unconnected DGRAM has no address to send to and
|
||||
// will be refused when it tries). AF_INET has datagrams too, and inet_new is
|
||||
// what decides whether it can serve the type asked for.
|
||||
if(domain == AF_UNIX && type != SOCK_STREAM)
|
||||
if(domain == AF_UNIX && type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET)
|
||||
return 0;
|
||||
// AF_NETLINK carries whole messages and musl asks for SOCK_RAW; SOCK_DGRAM is
|
||||
// accepted too because Linux does. NETLINK_ROUTE is the only protocol served
|
||||
|
|
@ -618,14 +756,16 @@ socknew(int domain, int type, int proto)
|
|||
if(domain == AF_NETLINK){
|
||||
if(type != SOCK_RAW && type != SOCK_DGRAM)
|
||||
return 0;
|
||||
if(proto != NETLINK_ROUTE)
|
||||
if(proto != NETLINK_ROUTE && proto != NETLINK_KOBJECT_UEVENT)
|
||||
return 0;
|
||||
}
|
||||
|
||||
acquire(&stab.lock);
|
||||
s = alloc_locked(type);
|
||||
if(s)
|
||||
if(s){
|
||||
s->domain = domain;
|
||||
s->nlproto = domain == AF_NETLINK ? proto : 0;
|
||||
}
|
||||
release(&stab.lock);
|
||||
if(s == 0)
|
||||
return 0;
|
||||
|
|
@ -644,16 +784,16 @@ socknew(int domain, int type, int proto)
|
|||
|
||||
// A connected pair with no name and no listener, for socketpair(2).
|
||||
int
|
||||
sockpair(struct socket **pa, struct socket **pb)
|
||||
sockpair_type(struct socket **pa, struct socket **pb, int type)
|
||||
{
|
||||
struct socket *a, *b;
|
||||
|
||||
acquire(&stab.lock);
|
||||
if((a = alloc_locked(SOCK_STREAM)) == 0){
|
||||
if((a = alloc_locked(type)) == 0){
|
||||
release(&stab.lock);
|
||||
return -ENFILE;
|
||||
}
|
||||
if((b = alloc_locked(SOCK_STREAM)) == 0){
|
||||
if((b = alloc_locked(type)) == 0){
|
||||
a->state = SS_FREE;
|
||||
a->ref = 0;
|
||||
release(&stab.lock);
|
||||
|
|
@ -674,6 +814,12 @@ sockpair(struct socket **pa, struct socket **pb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
sockpair(struct socket **pa, struct socket **pb)
|
||||
{
|
||||
return sockpair_type(pa, pb, SOCK_STREAM);
|
||||
}
|
||||
|
||||
// bind(2) for AF_UNIX. The caller has already created the filesystem node and
|
||||
// hands over its identity; see sys_bind() in kernel/sys/sys_net.c for why the
|
||||
// creation lives there and not here.
|
||||
|
|
|
|||
Loading…
Reference in a new issue