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:
Nexoniarz 2026-10-26 09:51:41 +01:00
commit a9f8cb7e0f
7 changed files with 457 additions and 40 deletions

View file

@ -39,6 +39,7 @@
#include "uapi/errno.h"
#include "fs/blkdev.h"
#include "dev/pty.h"
#include "core/uaccess.h"
// Inode numbering. Two directories and a flat table of nodes, so the number is
// either a directory id or an index into devnodes[] offset past them.
@ -57,6 +58,52 @@
#define DEVFS_PTS0 8192ULL
#define DEVFS_PTM0 12288ULL
// What udev adds to /dev: directories (/dev/input/by-path, /dev/disk/...) and
// symlinks (/dev/fd, /dev/stdin, /dev/char/13:64, /dev/disk/by-id/...). The
// device nodes themselves stay the kernel's; this is a small table of names
// that live only in memory, as they would in a tmpfs, listed after the real
// entries of whichever directory they are in. Nothing here can hold data: a
// regular file in /dev is refused.
#define DEVFS_EXTRA0 32768ULL
#define NEXTRA 192
#define EXTRA_NAME 48
#define EXTRA_TARGET 112
struct extra {
uchar used; // allocated (an inode exists or is about to)
uchar linked; // has a name in a directory
short type; // T_DIR or T_SYMLINK
uint64 parent; // inum of the directory it is in
ushort tlen;
char name[EXTRA_NAME];
char target[EXTRA_TARGET];
};
static struct {
struct spinlock lock;
struct extra e[NEXTRA];
} extras;
static int extras_ready;
static void
extras_init(void)
{
if (!extras_ready) {
initlock(&extras.lock, "devfsx");
extras_ready = 1;
}
}
static int
extra_of(uint64 inum)
{
if (inum < DEVFS_EXTRA0)
return -1;
uint64 i = inum - DEVFS_EXTRA0;
return i < NEXTRA ? (int)i : -1;
}
// Which directory a node lives in. Only two exist and a third would want a real
// tree rather than a flag, which is the point at which this file should grow
// one instead of adding DEV_DIR_THIRD.
@ -158,7 +205,21 @@ devfs_iload(struct inode *ip)
ip->major = ip->minor = 0;
ip->size = 0;
if (ip->inum == DEVFS_ROOT || ip->inum == DEVFS_INPUT ||
int x = extra_of(ip->inum);
if (x >= 0) {
extras_init();
acquire(&extras.lock);
if (extras.e[x].used) {
ip->type = extras.e[x].type;
ip->nlink = extras.e[x].type == T_DIR ? 2 : 1;
ip->size = extras.e[x].tlen;
ip->mode = extras.e[x].type == T_DIR ? 0755 : 0777;
} else {
ip->type = 0;
ip->nlink = 0;
}
release(&extras.lock);
} else if (ip->inum == DEVFS_ROOT || ip->inum == DEVFS_INPUT ||
ip->inum == DEVFS_PTS) {
ip->type = T_DIR;
ip->nlink = 2;
@ -193,16 +254,69 @@ devfs_iload(struct inode *ip)
}
static void devfs_istore(struct inode *ip) { (void)ip; }
static void devfs_ifree(struct inode *ip) { (void)ip; }
static void
devfs_ifree(struct inode *ip)
{
int x = extra_of(ip->inum);
if (x < 0)
return;
extras_init();
acquire(&extras.lock);
extras.e[x].used = 0;
release(&extras.lock);
}
static void devfs_truncate(struct inode *ip, uint64 n) { (void)ip; (void)n; }
// Never reached for a device node: fileread and filewrite see T_DEV and go
// through devsw. Present because fs_ops has the slots, and answering -EIO is
// better than a null pointer if something ever does arrive here.
static int devfs_read(struct inode *ip, int u, uint64 d, uint64 o, uint n)
{ (void)ip; (void)u; (void)d; (void)o; (void)n; return -1; }
static int devfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n)
{ (void)ip; (void)u; (void)s; (void)o; (void)n; return -1; }
static int
devfs_read(struct inode *ip, int u, uint64 d, uint64 o, uint n)
{
int x = extra_of(ip->inum);
if (x < 0 || ip->type != T_SYMLINK)
return -1;
extras_init();
char buf[EXTRA_TARGET];
acquire(&extras.lock);
uint len = extras.e[x].tlen;
memmove(buf, extras.e[x].target, len);
release(&extras.lock);
if (o >= len)
return 0;
if (o + n > len)
n = (uint)(len - o);
if (u) {
if (copy_to_user(d, buf + o, n) < 0)
return -EFAULT;
} else {
memmove((char *)d, buf + o, n);
}
return (int)n;
}
// Only a symlink's target can be written, once, from offset 0.
static int
devfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n)
{
int x = extra_of(ip->inum);
if (x < 0 || ip->type != T_SYMLINK || o != 0 || n >= EXTRA_TARGET)
return -1;
extras_init();
char buf[EXTRA_TARGET];
if (u) {
if (copy_from_user(buf, s, n) < 0)
return -EFAULT;
} else {
memmove(buf, (char *)s, n);
}
acquire(&extras.lock);
memmove(extras.e[x].target, buf, n);
extras.e[x].tlen = (ushort)n;
release(&extras.lock);
ip->size = n;
return (int)n;
}
static int
streq(const char *a, const char *b)
@ -211,12 +325,41 @@ streq(const char *a, const char *b)
return *a == *b;
}
static struct inode *
extra_find(uint64 parent, const char *name)
{
extras_init();
acquire(&extras.lock);
for (int i = 0; i < NEXTRA; i++)
if (extras.e[i].used && extras.e[i].linked && extras.e[i].parent == parent &&
streq(name, extras.e[i].name)) {
release(&extras.lock);
return iget(DEVFS_DEV, DEVFS_EXTRA0 + (uint64)i);
}
release(&extras.lock);
return 0;
}
static struct inode *
devfs_dirlookup(struct inode *dp, char *name, uint *poff)
{
if (poff)
*poff = 0;
int dx = extra_of(dp->inum);
if (dx >= 0) {
extras_init();
if (streq(name, "."))
return iget(DEVFS_DEV, dp->inum);
if (streq(name, "..")) {
acquire(&extras.lock);
uint64 up = extras.e[dx].parent;
release(&extras.lock);
return iget(DEVFS_DEV, up ? up : DEVFS_ROOT);
}
return extra_find(dp->inum, name);
}
if (streq(name, ".") || (streq(name, "..") && dp->inum == DEVFS_ROOT))
return iget(DEVFS_DEV, dp->inum);
if (streq(name, ".."))
@ -261,19 +404,85 @@ devfs_dirlookup(struct inode *dp, char *name, uint *poff)
if (streq(name, blkdev_name((uint)b)))
return iget(DEVFS_DEV, DEVFS_BLK0 + (uint64)b);
return extra_find(dp->inum, name);
}
// What can be created here: a directory or a symlink, by ialloc and then
// dirlink -- the order every filesystem is driven in. The device nodes
// themselves are the kernel's: mknod answers EROFS by never getting an inode.
static struct inode *
devfs_ialloc(uint dev, short type)
{
(void)dev;
if (type != T_DIR && type != T_SYMLINK)
return 0;
extras_init();
acquire(&extras.lock);
for (int i = 0; i < NEXTRA; i++)
if (!extras.e[i].used) {
memset(&extras.e[i], 0, sizeof(extras.e[i]));
extras.e[i].used = 1;
extras.e[i].type = type;
release(&extras.lock);
return iget(DEVFS_DEV, DEVFS_EXTRA0 + (uint64)i);
}
release(&extras.lock);
return 0;
}
// A read-only filesystem, and these say so rather than pretending. mknod on
// /dev returning EROFS is the correct answer: the node either exists because
// the kernel has the device, or it does not exist because it has not.
static int devfs_dirlink(struct inode *dp, char *name, uint64 inum)
{ (void)dp; (void)name; (void)inum; return -EROFS; }
static int devfs_dirunlink(struct inode *dp, char *name, uint off)
{ (void)dp; (void)name; (void)off; return -EROFS; }
static int devfs_dirempty(struct inode *dp) { (void)dp; return 0; }
static struct inode *devfs_ialloc(uint dev, short type)
{ (void)dev; (void)type; return 0; }
static int
devfs_dirlink(struct inode *dp, char *name, uint64 inum)
{
int x = extra_of(inum);
if (streq(name, ".") || streq(name, ".."))
return 0; /* a new directory's own dots */
if (x < 0 || strlen(name) >= EXTRA_NAME)
return -EINVAL;
extras_init();
acquire(&extras.lock);
if (!extras.e[x].used || extras.e[x].linked) {
release(&extras.lock);
return -EINVAL;
}
extras.e[x].parent = dp->inum;
extras.e[x].linked = 1;
memmove(extras.e[x].name, name, strlen(name) + 1);
release(&extras.lock);
return 0;
}
static int
devfs_dirempty(struct inode *dp)
{
if (extra_of(dp->inum) < 0)
return 0;
extras_init();
acquire(&extras.lock);
for (int i = 0; i < NEXTRA; i++)
if (extras.e[i].used && extras.e[i].linked && extras.e[i].parent == dp->inum) {
release(&extras.lock);
return 0;
}
release(&extras.lock);
return 1;
}
static int
devfs_dirunlink(struct inode *dp, char *name, uint off)
{
(void)off;
extras_init();
acquire(&extras.lock);
for (int i = 0; i < NEXTRA; i++)
if (extras.e[i].used && extras.e[i].linked && extras.e[i].parent == dp->inum &&
streq(name, extras.e[i].name)) {
extras.e[i].linked = 0; /* the inode goes when nobody holds it */
release(&extras.lock);
return 0;
}
release(&extras.lock);
return -EROFS; /* a device node is the kernel's */
}
struct linux_dirent64 {
uint64 d_ino;
@ -312,8 +521,9 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
int used = 0;
uint64 n = *poff;
int isx = extra_of(dp->inum) >= 0;
if (dp->inum != DEVFS_ROOT && dp->inum != DEVFS_INPUT &&
dp->inum != DEVFS_PTS)
dp->inum != DEVFS_PTS && !isx)
return -1;
if (n == 0) {
@ -326,6 +536,25 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
n++;
}
// A directory udev made: the dots, then what is in it.
if (isx) {
uint64 base = 2;
extras_init();
acquire(&extras.lock);
for (int i = 0; i < NEXTRA; i++) {
if (!extras.e[i].used || !extras.e[i].linked || extras.e[i].parent != dp->inum)
continue;
if (n > base) { base++; continue; }
if (emit(out, cap, &used, DEVFS_EXTRA0 + (uint64)i,
extras.e[i].type == T_DIR ? 4 : 10 /* DT_LNK */, extras.e[i].name) < 0)
break;
n++;
base++;
}
release(&extras.lock);
goto out;
}
// /dev/pts: the dots, then the pairs that exist. Ordinal 2 + N is pair N,
// whether or not the ones before it exist, so a pair opened between two
// calls does not shift the rest.
@ -378,6 +607,21 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
base++;
}
// What udev added to this directory, after everything the kernel put there.
extras_init();
acquire(&extras.lock);
for (int i = 0; i < NEXTRA; i++) {
if (!extras.e[i].used || !extras.e[i].linked || extras.e[i].parent != dp->inum)
continue;
if (n > base) { base++; continue; }
if (emit(out, cap, &used, DEVFS_EXTRA0 + (uint64)i,
extras.e[i].type == T_DIR ? 4 : 10, extras.e[i].name) < 0)
break;
n++;
base++;
}
release(&extras.lock);
out:
*poff = n;
return used;