From 5d4d318ebf58977f4655aa8f012fb92f462c81ab Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Mon, 26 Oct 2026 11:39:13 +0100 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao --- Makefile | 2 +- include/core/defs.h | 8 +- include/dev/kdev.h | 44 ++++++++ include/net/socket.h | 5 + kernel/dev/kdev.c | 240 +++++++++++++++++++++++++++++++++++++++ kernel/fs/devfs.c | 21 ++++ kernel/fs/sysfs.c | 263 ++++++++++++++++++++++++++++++++++++++++++- kernel/net/unix.c | 157 +++++++++++++++++++++----- kernel/sys/sys_fs.c | 10 ++ kernel/sys/sys_net.c | 100 +++++++++++++++- 10 files changed, 813 insertions(+), 37 deletions(-) create mode 100644 include/dev/kdev.h create mode 100644 kernel/dev/kdev.c diff --git a/Makefile b/Makefile index 70dc3e3..f5696b4 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ KERN_CORE = $(addprefix $(BUILD)/kernel/core/, \ KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \ bio.o blkdev.o cdev.o device.o disk.o fb.o ide.o input.o irq.o kbd.o keymap.o mouse.o \ pci.o pciuser.o text.o ttf.o uart.o gfx.o vt.o ramdisk.o procfs.o \ - dbg.o netuser.o audio.o dmi.o pty.o) + dbg.o netuser.o audio.o dmi.o pty.o kdev.o) # Drivers for hardware that has to be *found* rather than assumed. Anything # discovered by walking a bus belongs here; the fixed-address legacy devices diff --git a/include/core/defs.h b/include/core/defs.h index 8fd7ca5..6b2fb10 100644 --- a/include/core/defs.h +++ b/include/core/defs.h @@ -541,7 +541,13 @@ int sock_shutdown(struct socket*, int how); int sockpair(struct socket **a, struct socket **b); int sockpair_type(struct socket **a, struct socket **b, int type); int sock_is_packet(struct socket *s); -int sock_netlink_bind(struct socket *s, uint groups); +uint sock_nl_groups(struct socket *s); +int sock_netlink_bind(struct socket *s, uint pid, uint groups); +int sock_uevent_send(struct socket *s, const char *msg, int len, uint dpid, uint dgroups); +uint sock_nl_from(struct socket *s); +uint sock_nl_pid(struct socket *s); +uint sock_nl_bound_groups(struct socket *s); +void dev_stat_numbers(int major, int minor, int *maj, int *min); void uevent_send(uint groups, const char *msg, int len, struct socket *from); const char* sockname(struct socket*); // AF_INET addressing. Separate from sockbind/sockconnect because the families diff --git a/include/dev/kdev.h b/include/dev/kdev.h new file mode 100644 index 0000000..6cedaaa --- /dev/null +++ b/include/dev/kdev.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// The devices the kernel implements itself, as /sys and udev see them. +// +// /dev holds them already (kernel/fs/devfs.c). This is the other half of what +// udev needs: for each node a place in /sys/devices, the class and dev/ links +// that lead back to it, the facts udev's rules read (MAJOR, MINOR, DEVNAME, +// input capabilities), and a way to announce it on the uevent netlink socket. +#pragma once + +#include "core/types.h" + +#define KDEV_NAME 24 +#define KDEV_DEVNAME 32 +#define KDEV_BLK0 256 // kdev indexes from here are block devices + +enum { KD_NONE, KD_KEYBOARD, KD_MOUSE }; + +struct kdev { + char subsys[16]; // class: input, mem, tty, graphics, block + char name[KDEV_NAME]; // directory name under the parent + char devname[KDEV_DEVNAME]; // path below /dev, "" for a device with no node + short major, minor; + int block; + int parent; // kdev index of the device it hangs off, or -1 + int cap; // KD_*: what an input device can do +}; + +int kdev_nsubsys(void); +const char *kdev_subsys(int s); + +// Index of the Nth device that exists, 0.. until -1. +int kdev_nth(int n); +int kdev_get(int i, struct kdev *k); +// The numbers stat() shows for its node, which are what /sys must show too. +void kdev_numbers(const struct kdev *k, int *maj, int *min); + +// "devices/virtual/input/input0/event0": where it lives below /sys. +int kdev_path(int i, char *buf, int cap); + +// Announce a device on the uevent socket: action is "add", "remove", "change". +void kdev_uevent(int i, const char *action); +// What reading a device's `uevent` file says. +int kdev_uevent_text(int i, char *buf, int cap); diff --git a/include/net/socket.h b/include/net/socket.h index 812e0d6..583f140 100644 --- a/include/net/socket.h +++ b/include/net/socket.h @@ -344,6 +344,11 @@ struct socket { // AF_NETLINK only: which protocol, and the multicast groups bound. int nlproto; uint nlgroups; + uint nlpid; // port id, assigned at bind + uint nllast; // groups of the datagram read last + uint nlfrom; // port id (netlink) or pid (Unix) of its sender + uint pk_uid, pk_gid; // Unix packet: credentials of its sender + int pk_valid; // a packet has been read // Connections waiting to be accepted, on a listener. struct socket *queue[NBACKLOG]; diff --git a/kernel/dev/kdev.c b/kernel/dev/kdev.c new file mode 100644 index 0000000..70d3471 --- /dev/null +++ b/kernel/dev/kdev.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// See include/dev/kdev.h. + +#include "core/types.h" +#include "core/defs.h" +#include "core/param.h" +#include "core/spinlock.h" +#include "core/sleeplock.h" +#include "fs/fs.h" +#include "fs/blkdev.h" +#include "dev/kdev.h" +#include "dev/pty.h" +#include "net/socket.h" + +static const char *const subsys[] = { + "input", "mem", "tty", "graphics", "block", +}; +#define NSUBSYS ((int)(sizeof subsys / sizeof subsys[0])) + +#define NO_PARENT (-1) + +// Majors and minors are the ones /dev reports, devsw indices and all -- the +// devnodes table in kernel/fs/devfs.c. udev compares what it reads here with +// what stat() says about the node, so these have to be the same numbers. +static const struct kdev table[] = { + { "input", "input0", "", 0, 0, 0, NO_PARENT, KD_KEYBOARD }, + { "input", "event0", "input/event0", 7, 0, 0, 0, KD_NONE }, + { "input", "input1", "", 0, 0, 0, NO_PARENT, KD_MOUSE }, + { "input", "event1", "input/event1", 7, 1, 0, 2, KD_NONE }, + + { "mem", "null", "null", 1, 2, 0, NO_PARENT, KD_NONE }, + { "mem", "zero", "zero", 1, 5, 0, NO_PARENT, KD_NONE }, + { "mem", "full", "full", 1, 7, 0, NO_PARENT, KD_NONE }, + { "mem", "random", "random", 1, 8, 0, NO_PARENT, KD_NONE }, + { "mem", "urandom", "urandom", 1, 9, 0, NO_PARENT, KD_NONE }, + + { "tty", "console", "console", 1, 1, 0, NO_PARENT, KD_NONE }, + { "tty", "tty", "tty", 1, 10, 0, NO_PARENT, KD_NONE }, + { "tty", "tty0", "tty0", 1, 0, 0, NO_PARENT, KD_NONE }, + { "tty", "tty1", "tty1", 1, 11, 0, NO_PARENT, KD_NONE }, + { "tty", "tty2", "tty2", 1, 12, 0, NO_PARENT, KD_NONE }, + { "tty", "tty3", "tty3", 1, 13, 0, NO_PARENT, KD_NONE }, + { "tty", "tty4", "tty4", 1, 14, 0, NO_PARENT, KD_NONE }, + { "tty", "tty5", "tty5", 1, 15, 0, NO_PARENT, KD_NONE }, + { "tty", "tty6", "tty6", 1, 16, 0, NO_PARENT, KD_NONE }, + { "tty", "tty7", "tty7", 1, 17, 0, NO_PARENT, KD_NONE }, + { "tty", "ptmx", "ptmx", PTMX_MAJOR, 0, 0, NO_PARENT, KD_NONE }, + + { "graphics", "fb0", "fb0", 2, 0, 0, NO_PARENT, KD_NONE }, +}; +#define NTABLE ((int)(sizeof table / sizeof table[0])) + +int kdev_nsubsys(void) { return NSUBSYS; } +const char *kdev_subsys(int s) { return s >= 0 && s < NSUBSYS ? subsys[s] : 0; } + +int +kdev_nth(int n) +{ + if (n < NTABLE) + return n < 0 ? -1 : n; + n -= NTABLE; + for (int k = 0, b; (b = blkdev_nth(k)) >= 0; k++) + if (n-- == 0) + return KDEV_BLK0 + b; + return -1; +} + +static void +copy(char *d, const char *s, int cap) +{ + int i = 0; + while (s[i] && i < cap - 1) { d[i] = s[i]; i++; } + d[i] = 0; +} + +int +kdev_get(int i, struct kdev *k) +{ + if (i >= 0 && i < NTABLE) { + *k = table[i]; + return 0; + } + if (i >= KDEV_BLK0 && blkdev_present((uint)(i - KDEV_BLK0))) { + memset(k, 0, sizeof(*k)); + copy(k->subsys, "block", sizeof k->subsys); + copy(k->name, blkdev_name((uint)(i - KDEV_BLK0)), sizeof k->name); + copy(k->devname, k->name, sizeof k->devname); + k->major = 8; + k->minor = (short)(i - KDEV_BLK0); + k->block = 1; + k->parent = NO_PARENT; + return 0; + } + return -1; +} + +void +kdev_numbers(const struct kdev *k, int *maj, int *min) +{ + if (k->block) { + *maj = k->major; + *min = k->minor; + return; + } + dev_stat_numbers(k->major, k->minor, maj, min); +} + +int +kdev_path(int i, char *buf, int cap) +{ + struct kdev k; + char tmp[160]; + int n = 0; + + if (kdev_get(i, &k) < 0) + return -1; + // The top ancestor names the subsystem directory; below it, one directory + // per level. + int chain[4], depth = 0; + for (int c = i; c >= 0 && depth < 4; ) { + struct kdev ck; + if (kdev_get(c, &ck) < 0) + break; + chain[depth++] = c; + c = ck.parent; + } + copy(tmp, "devices/virtual/", sizeof tmp); + n = (int)strlen(tmp); + { + struct kdev top; + kdev_get(chain[depth - 1], &top); + copy(tmp + n, top.subsys, (int)sizeof tmp - n); + n += (int)strlen(tmp + n); + } + for (int d = depth - 1; d >= 0 && n < (int)sizeof tmp - 2; d--) { + struct kdev ck; + kdev_get(chain[d], &ck); + tmp[n++] = '/'; + copy(tmp + n, ck.name, (int)sizeof tmp - n); + n += (int)strlen(tmp + n); + } + copy(buf, tmp, cap); + return (int)strlen(buf); +} + +static uint kseq; + +static int +put(char *b, int cap, int n, const char *s) +{ + while (*s && n < cap - 1) + b[n++] = *s++; + return n; +} + +static int +putnum(char *b, int cap, int n, uint v) +{ + char t[12]; + int k = 0; + do { t[k++] = '0' + v % 10; v /= 10; } while (v); + while (k && n < cap - 1) + b[n++] = t[--k]; + return n; +} + +// KEY=value pairs, NUL separated, as the kernel sends them; `nl` swaps the +// NULs for newlines, which is what the uevent file shows. +static int +vars(int i, char *buf, int cap, const char *action, int nl) +{ + struct kdev k; + char path[160]; + int n = 0; + char sep = nl ? '\n' : 0; + + if (kdev_get(i, &k) < 0 || kdev_path(i, path, sizeof path) < 0) + return -1; + if (action) { + n = put(buf, cap, n, "ACTION="); + n = put(buf, cap, n, action); + buf[n++] = sep; + } + n = put(buf, cap, n, "DEVPATH=/"); + n = put(buf, cap, n, path); + buf[n++] = sep; + n = put(buf, cap, n, "SUBSYSTEM="); + n = put(buf, cap, n, k.subsys); + buf[n++] = sep; + if (action) { + n = put(buf, cap, n, "SEQNUM="); + n = putnum(buf, cap, n, ++kseq); + buf[n++] = sep; + } + if (k.devname[0]) { + int maj, min; + kdev_numbers(&k, &maj, &min); + n = put(buf, cap, n, "MAJOR="); + n = putnum(buf, cap, n, (uint)maj); + buf[n++] = sep; + n = put(buf, cap, n, "MINOR="); + n = putnum(buf, cap, n, (uint)min); + buf[n++] = sep; + n = put(buf, cap, n, "DEVNAME="); + n = put(buf, cap, n, k.devname); + buf[n++] = sep; + } + if (k.block) { + n = put(buf, cap, n, "DEVTYPE=disk"); + buf[n++] = sep; + } + return n; +} + +int +kdev_uevent_text(int i, char *buf, int cap) +{ + return vars(i, buf, cap, 0, 1); +} + +void +kdev_uevent(int i, const char *action) +{ + char msg[512]; + char path[160]; + int n = 0; + + if (kdev_path(i, path, sizeof path) < 0) + return; + n = put(msg, sizeof msg, n, action); + msg[n++] = '@'; + n = put(msg, sizeof msg, n, "/"); + n = put(msg, sizeof msg, n, path); + msg[n++] = 0; + int v = vars(i, msg + n, (int)sizeof msg - n, action, 0); + if (v < 0) + return; + uevent_send(UEVENT_GROUP_KERNEL, msg, n + v, 0); +} diff --git a/kernel/fs/devfs.c b/kernel/fs/devfs.c index d1ee50d..f802c76 100644 --- a/kernel/fs/devfs.c +++ b/kernel/fs/devfs.c @@ -274,6 +274,25 @@ static int devfs_read(struct inode *ip, int u, uint64 d, uint64 o, uint n) { int x = extra_of(ip->inum); + // mkdir lays down the "." and ".." of a v1-style directory by reading and + // rewriting its first two entries (build_dir_tree). A directory here has + // none stored, so the read gives zeros and the write is swallowed. + if (x >= 0 && ip->type == T_DIR) { + char z[64]; + memset(z, 0, sizeof z); + for (uint done = 0; done < n; ) { + uint c = n - done < sizeof z ? n - done : (uint)sizeof z; + if (u) { + if (copy_to_user(d + done, z, c) < 0) + return -EFAULT; + } else { + memmove((char *)d + done, z, c); + } + done += c; + } + (void)o; + return (int)n; + } if (x < 0 || ip->type != T_SYMLINK) return -1; extras_init(); @@ -300,6 +319,8 @@ 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_DIR) + return (int)n; // see devfs_read if (x < 0 || ip->type != T_SYMLINK || o != 0 || n >= EXTRA_TARGET) return -1; extras_init(); diff --git a/kernel/fs/sysfs.c b/kernel/fs/sysfs.c index 6718e28..796d6a4 100644 --- a/kernel/fs/sysfs.c +++ b/kernel/fs/sysfs.c @@ -45,6 +45,7 @@ #include "core/hwinfo.h" #include "gfx/gfx.h" #include "dev/acpi.h" +#include "dev/kdev.h" // --------------------------------------------------------------------------- // Inode numbers: kind, index, file -- the whole identity, since iget gives @@ -59,6 +60,16 @@ #define K_DMIID 6ULL // file a DMI_* field + 1 #define K_DMITAB 7ULL // file 1 the entry point, 2 the table #define K_PSU 8ULL // idx an acpi_psu_get() index; file a psufiles[] index +#define K_KDEV 9ULL // idx a kdev index; file 0 the directory, KF_* its files +#define K_KSUBV 10ULL // idx a kdev subsystem: /sys/devices/virtual/ +#define K_KCLASS 11ULL // idx a kdev subsystem: /sys/class/ +#define K_KCLINK 12ULL // idx a kdev: the symlink in /sys/class/ +#define K_KDEVD 13ULL // idx 0 char, 1 block: /sys/dev/char, /sys/dev/block +#define K_KDEVL 14ULL // idx a kdev: the "MAJ:MIN" symlink in /sys/dev/* + +// Files in a kdev directory (K_KDEV). +enum { KF_DIR, KF_DEV, KF_UEVENT, KF_SUBSYSTEM, KF_NAME, KF_PROPERTIES, + KF_CAPS, KF_EV, KF_KEY, KF_REL, KF_ABS, KF_N }; #define INO(k, idx, f) (((uint64)(k) << 40) | ((uint64)(idx) << 8) | (uint64)(f)) #define I_KIND(i) ((int)((i) >> 40)) @@ -68,6 +79,7 @@ enum { D_ROOT, D_BUS, D_PCI, D_PCIDEV, D_USB, D_USBDEV, D_FW, D_ACPI, D_ACPITAB, D_DMI, D_DMITAB, D_MEMMAP, D_EFI, D_CLASS, D_CLASSDMI, D_DMIID, D_PSU, + D_DEVICES, D_VIRTUAL, D_DEVT, D_NDIRS }; @@ -89,6 +101,9 @@ static const struct { const char *name; int parent; } dirs[D_NDIRS] = { [D_CLASSDMI] = { "dmi", D_CLASS }, [D_DMIID] = { "id", D_CLASSDMI }, [D_PSU] = { "power_supply", D_CLASS }, + [D_DEVICES] = { "devices", D_ROOT }, + [D_VIRTUAL] = { "virtual", D_DEVICES }, + [D_DEVT] = { "dev", D_ROOT }, }; #define SYSFS_ROOT INO(K_DIR, D_ROOT, 0) @@ -387,6 +402,96 @@ gen_psu(struct out *o, int idx, int f) } } +// ---- kernel-implemented devices (include/dev/kdev.h) ---- + +static int +kd_subsys_of(const struct kdev *k) +{ + for (int s = 0; s < kdev_nsubsys(); s++) { + const char *n = kdev_subsys(s), *m = k->subsys; + while (*n && *n == *m) { n++; m++; } + if (*n == *m) + return s; + } + return -1; +} + +static void +kd_devt(const struct kdev *k, char *name) +{ + struct out o = { name, 0, 30 }; + int maj, min; + kdev_numbers(k, &maj, &min); + putu(&o, (uint64)maj); + putc_(&o, ':'); + putu(&o, (uint64)min); + name[o.n] = 0; +} + +// Does file f exist in kdev k's directory? +static int +kd_has(const struct kdev *k, int f) +{ + switch (f) { + case KF_DEV: return k->devname[0] != 0; + case KF_UEVENT: + case KF_SUBSYSTEM: return 1; + case KF_NAME: case KF_PROPERTIES: case KF_CAPS: + return k->cap != KD_NONE; + } + return 0; +} + +static int +kd_children(uint64 dino, child_fn cb, void *ctx) +{ + int kind = I_KIND(dino), idx = I_IDX(dino); + char name[32]; + struct kdev k; + + if (kind == K_KSUBV) { + for (int n = 0, i; (i = kdev_nth(n)) >= 0; n++) + if (kdev_get(i, &k) == 0 && k.parent < 0 && kd_subsys_of(&k) == idx && + cb(ctx, INO(K_KDEV, i, 0), 1, k.name)) + return 1; + } else if (kind == K_KCLASS) { + for (int n = 0, i; (i = kdev_nth(n)) >= 0; n++) + if (kdev_get(i, &k) == 0 && kd_subsys_of(&k) == idx && + cb(ctx, INO(K_KCLINK, i, 0), 2, k.name)) + return 1; + } else if (kind == K_KDEVD) { + for (int n = 0, i; (i = kdev_nth(n)) >= 0; n++) + if (kdev_get(i, &k) == 0 && k.devname[0] && k.block == idx) { + kd_devt(&k, name); + if (cb(ctx, INO(K_KDEVL, i, 0), 2, name)) + return 1; + } + } else if (kind == K_KDEV && I_FILE(dino) == KF_DIR) { + if (kdev_get(idx, &k) < 0) + return 0; + for (int f = KF_DEV; f <= KF_CAPS; f++) + if (kd_has(&k, f)) { + static const char *const names[] = { 0, "dev", "uevent", "subsystem", + "name", "properties", "capabilities" }; + if (cb(ctx, INO(K_KDEV, idx, f), + f == KF_CAPS ? 1 : f == KF_SUBSYSTEM ? 2 : 0, names[f])) + return 1; + } + for (int n = 0, i; (i = kdev_nth(n)) >= 0; n++) { + struct kdev c; + if (kdev_get(i, &c) == 0 && c.parent == idx && + cb(ctx, INO(K_KDEV, i, 0), 1, c.name)) + return 1; + } + } else if (kind == K_KDEV && I_FILE(dino) == KF_CAPS) { + static const char *const names[] = { "ev", "key", "rel", "abs" }; + for (int f = KF_EV; f <= KF_ABS; f++) + if (cb(ctx, INO(K_KDEV, idx, f), 0, names[f - KF_EV])) + return 1; + } + return 0; +} + static int children(uint64 dino, child_fn cb, void *ctx) { @@ -403,6 +508,21 @@ children(uint64 dino, child_fn cb, void *ctx) return 1; } switch (idx) { + case D_VIRTUAL: + for (int s = 0; s < kdev_nsubsys(); s++) + if (cb(ctx, INO(K_KSUBV, s, 0), 1, kdev_subsys(s))) + return 1; + break; + case D_DEVT: + if (cb(ctx, INO(K_KDEVD, 0, 0), 1, "char") || + cb(ctx, INO(K_KDEVD, 1, 0), 1, "block")) + return 1; + break; + case D_CLASS: + for (int s = 0; s < kdev_nsubsys(); s++) + if (cb(ctx, INO(K_KCLASS, s, 0), 1, kdev_subsys(s))) + return 1; + break; case D_PCIDEV: for (int i = 0; i < pci_count(); i++) { struct out o = { name, 0, sizeof name - 1 }; @@ -486,6 +606,9 @@ children(uint64 dino, child_fn cb, void *ctx) for (int f = 1; f < NPSUFILES; f++) if (psu_has(&b, f) && cb(ctx, INO(K_PSU, idx, f), 0, psufiles[f])) return 1; + } else if (kind == K_KSUBV || kind == K_KCLASS || kind == K_KDEVD || + kind == K_KDEV) { + return kd_children(dino, cb, ctx); } else if (kind == K_MEMMAP && I_FILE(dino) == 0) { for (int f = 1; f < 4; f++) if (cb(ctx, INO(K_MEMMAP, idx, f), 0, memfiles[f])) @@ -506,6 +629,19 @@ parent_of(uint64 dino) case K_USB: return INO(K_DIR, D_USBDEV, 0); case K_MEMMAP: return INO(K_DIR, D_MEMMAP, 0); case K_PSU: return INO(K_DIR, D_PSU, 0); + case K_KSUBV: return INO(K_DIR, D_VIRTUAL, 0); + case K_KCLASS: return INO(K_DIR, D_CLASS, 0); + case K_KDEVD: return INO(K_DIR, D_DEVT, 0); + case K_KDEV: { + struct kdev k; + if (I_FILE(dino) == KF_CAPS) + return INO(K_KDEV, I_IDX(dino), 0); + if (kdev_get(I_IDX(dino), &k) < 0) + return SYSFS_ROOT; + if (k.parent >= 0) + return INO(K_KDEV, k.parent, 0); + return INO(K_KSUBV, kd_subsys_of(&k), 0); + } } return SYSFS_ROOT; } @@ -687,6 +823,67 @@ text(uint64 ino, char *buf, int cap) putc_(&o, '\n'); return o.n; } + case K_KDEV: { + struct kdev k; + char path[160]; + if (kdev_get(idx, &k) < 0 || f < KF_DEV || f >= KF_N || f == KF_CAPS || + (f != KF_UEVENT && f != KF_SUBSYSTEM && !kd_has(&k, f) && + !(f >= KF_EV && k.cap != KD_NONE))) + return -1; + switch (f) { + case KF_DEV: + kd_devt(&k, buf); + o.n = (int)strlen(buf); + putc_(&o, '\n'); + return o.n; + case KF_UEVENT: + return kdev_uevent_text(idx, buf, cap); + case KF_SUBSYSTEM: { + // ../ once for each directory between here and /sys, then class/. + if (kdev_path(idx, path, sizeof path) < 0) + return -1; + for (const char *p = path; ; p++) { + if (*p == '/' || *p == 0) + puts_(&o, "../"); + if (*p == 0) + break; + } + puts_(&o, "class/"); + puts_(&o, k.subsys); + return o.n; + } + case KF_NAME: + puts_(&o, k.cap == KD_KEYBOARD ? "SchOS keyboard\n" : "SchOS mouse\n"); + return o.n; + case KF_PROPERTIES: + puts_(&o, "0\n"); + return o.n; + case KF_EV: + puts_(&o, k.cap == KD_KEYBOARD ? "120013\n" : "17\n"); + return o.n; + case KF_KEY: + puts_(&o, k.cap == KD_KEYBOARD + ? "ffffffffffffffff ffffffffffffffff ffffffffffffffff fffffffffffffffe\n" + : "70000 0 0 0 0\n"); + return o.n; + case KF_REL: + puts_(&o, k.cap == KD_MOUSE ? "903\n" : "0\n"); + return o.n; + case KF_ABS: + puts_(&o, "0\n"); + return o.n; + } + return -1; + } + case K_KCLINK: + case K_KDEVL: { + char path[160]; + if (kdev_path(idx, path, sizeof path) < 0) + return -1; + puts_(&o, "../../"); + puts_(&o, path); + return o.n; + } case K_DMIID: { int n = dmi_field(f - 1, buf, cap - 1); if (n < 0) @@ -722,10 +919,30 @@ exists_dir(uint64 ino) } case K_PSU: return I_IDX(ino) < acpi_psu_count(); + case K_KSUBV: + case K_KCLASS: + return I_IDX(ino) < kdev_nsubsys(); + case K_KDEVD: + return I_IDX(ino) < 2; + case K_KDEV: { + struct kdev k; + if (kdev_get(I_IDX(ino), &k) < 0) + return 0; + return I_FILE(ino) == KF_DIR || (I_FILE(ino) == KF_CAPS && k.cap != KD_NONE); + } } return 0; } +// Symlinks: the class and dev/ entries, and a device's own `subsystem`. +static int +is_link(uint64 ino) +{ + int k = I_KIND(ino); + return k == K_KCLINK || k == K_KDEVL || + (k == K_KDEV && I_FILE(ino) == KF_SUBSYSTEM); +} + static void sysfs_iload(struct inode *ip) { @@ -740,13 +957,24 @@ sysfs_iload(struct inode *ip) ip->size = 0; ip->nlink = 1; - int subdir = I_KIND(ino) == K_PCI && - (I_FILE(ino) == PCI_GRAPHICS || I_FILE(ino) == PCI_FB0); + int subdir = (I_KIND(ino) == K_PCI && + (I_FILE(ino) == PCI_GRAPHICS || I_FILE(ino) == PCI_FB0)) || + (I_KIND(ino) == K_KDEV && I_FILE(ino) == KF_CAPS); if ((I_FILE(ino) == 0 || subdir) && exists_dir(ino)) { ip->type = T_DIR; ip->nlink = 2; return; } + if (is_link(ino)) { + char lb[200]; + int ln = text(ino, lb, sizeof lb); + if (ln >= 0) { + ip->type = T_SYMLINK; + ip->size = (uint64)ln; // readlink(2) trusts this length + ip->mode = 0777; + return; + } + } int n = binary(ino, 0, 0); if (n >= 0) { ip->type = T_FILE; @@ -761,7 +989,10 @@ sysfs_iload(struct inode *ip) kmfree(buf); if (n >= 0) { ip->type = T_FILE; - if (I_KIND(ino) == K_PCI && I_FILE(ino) == PCI_ENABLE) + if ((I_KIND(ino) == K_PCI && (I_FILE(ino) == PCI_ENABLE || + I_FILE(ino) == 10 /* uevent */)) || + (I_KIND(ino) == K_USB && I_FILE(ino) == NUSBFILES - 1) || + (I_KIND(ino) == K_KDEV && I_FILE(ino) == KF_UEVENT)) ip->rwmode = 3; return; } @@ -822,9 +1053,31 @@ sysfs_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n) static int sysfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n) { - (void)u; (void)s; (void)o; + (void)o; if (I_KIND(ip->inum) == K_PCI && I_FILE(ip->inum) == PCI_ENABLE) return (int)n; + // `echo add > uevent`: udevadm trigger asks the kernel to say again what it + // said when the device appeared. Only the kernel's own devices are announced; + // a PCI or USB device's uevent accepts the write and says nothing, because + // their /sys paths are not the /devices tree an event's DEVPATH must name. + if (I_KIND(ip->inum) == K_PCI || I_KIND(ip->inum) == K_USB) + return (int)n; + if (I_KIND(ip->inum) == K_KDEV && I_FILE(ip->inum) == KF_UEVENT) { + char act[16]; + uint k = n < sizeof(act) - 1 ? n : (uint)sizeof(act) - 1; + if (u) { + if (copy_from_user(act, s, k) < 0) + return -EFAULT; + } else { + memmove(act, (void *)s, k); + } + uint e = 0; + while (e < k && act[e] != '\n' && act[e] != ' ') + e++; + act[e] = 0; + kdev_uevent(I_IDX(ip->inum), e ? act : "change"); + return (int)n; + } return -EROFS; } @@ -896,7 +1149,7 @@ emit_cb(void *ctx, uint64 ino, int isdir, const char *name) de->d_ino = ino; de->d_off = l->used + rec; de->d_reclen = (ushort)rec; - de->d_type = isdir ? 4 : 8; + de->d_type = isdir == 2 ? 10 : isdir ? 4 : 8; memmove(de->d_name, name, nlen); memset(de->d_name + nlen, 0, rec - (int)sizeof(*de) - nlen); l->used += rec; diff --git a/kernel/net/unix.c b/kernel/net/unix.c index 2429ce6..84adce7 100644 --- a/kernel/net/unix.c +++ b/kernel/net/unix.c @@ -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; diff --git a/kernel/sys/sys_fs.c b/kernel/sys/sys_fs.c index 54baf20..71c9a5a 100644 --- a/kernel/sys/sys_fs.c +++ b/kernel/sys/sys_fs.c @@ -133,6 +133,16 @@ stati_linux(struct inode *ip, struct linux_stat *st) st->st_atime = ip->atime; st->st_mtime = ip->mtime; st->st_ctime = ip->ctime; } +// The number stat() reports for a device node, split back into major and +// minor. /sys must agree with stat() -- udev matches one against the other. +void +dev_stat_numbers(int major, int minor, int *maj, int *min) +{ + uint64 r = linux_tty_rdev((uint32)major, (uint32)minor); + *maj = (int)(((r >> 8) & 0xfff) | ((r >> 32) & ~0xfffULL)); + *min = (int)((r & 0xff) | ((r >> 12) & ~0xffULL)); +} + // ----------------------------------------------------------------------- // Internal helpers // ----------------------------------------------------------------------- diff --git a/kernel/sys/sys_net.c b/kernel/sys/sys_net.c index 34185a5..8e5456b 100644 --- a/kernel/sys/sys_net.c +++ b/kernel/sys/sys_net.c @@ -24,6 +24,7 @@ #include "uapi/posix.h" #include "core/syscall.h" #include "core/uaccess.h" +#include "core/kmem.h" #include "uapi/errno.h" #include "net/socket.h" @@ -272,12 +273,13 @@ SYSCALL_DEFINE(bind) // sockaddr_nl: family, pad, port id, groups. Only the groups matter here. if (sock_domain(s) == AF_NETLINK) { - uint groups = 0; + uint pid = 0, groups = 0; if (ulen < 12) return -EINVAL; - if (copy_from_user(&groups, uaddr + 8, 4) < 0) + if (copy_from_user(&pid, uaddr + 4, 4) < 0 || + copy_from_user(&groups, uaddr + 8, 4) < 0) return -EFAULT; - return sock_netlink_bind(s, groups); + return sock_netlink_bind(s, pid, groups); } uint64 key; @@ -608,6 +610,9 @@ SYSCALL_DEFINE(recvfrom) // believes a message, and with no address at all it hit an assertion // and every GNOME program that watches the network aborted. uint16 nl[6] = { AF_NETLINK, 0, 0, 0, 0, 0 }; + uint g = sock_nl_groups(s), from = sock_nl_from(s); + nl[2] = (uint16)from; nl[3] = (uint16)(from >> 16); + nl[4] = (uint16)g; nl[5] = (uint16)(g >> 16); alen = (int)sizeof(nl); int cap = alen; if (ulenp && get_user(cap, ulenp) < 0) @@ -666,6 +671,12 @@ sock_name(int peer) // A sockaddr_nl of zeros: family, padding, port id 0 (the kernel), // no groups. sa.un.sun_family = AF_NETLINK; + { + // Port id and bound groups, at their offsets in sockaddr_nl. + uint pid = sock_nl_pid(s), grp = sock_nl_bound_groups(s); + memmove((char *)&sa + 4, &pid, 4); + memmove((char *)&sa + 8, &grp, 4); + } alen = 12; } else { if ((r = sockname_unix(s, peer, sa.un.sun_path)) < 0) @@ -971,6 +982,7 @@ SYSCALL_DEFINE(sendmsg) uint ip = 0; ushort port = 0; int addressed = 0; + int anydata = 0; if ((r = argsockfile(0, &s, &file)) < 0) return r; @@ -996,6 +1008,61 @@ SYSCALL_DEFINE(sendmsg) addressed = 1; } + // A datagram socket sends the whole message as one datagram, however many + // iovecs it is spread over -- udev's header and properties are two. + if (sock_is_packet(s)) { + char *buf = kalloc(); // a page: kmalloc has no 4 KB class + int total = 0; + if (!buf) { + r = -ENOMEM; + goto fail; + } + for (i = 0; i < m.msg_iovlen; i++) { + char *p; + int len; + if ((len = fetch_iov(&m, i, &p)) < 0) { + kfree(buf); + r = len; + goto fail; + } + if (total + len > 4096 - 8) { + kfree(buf); + r = -EMSGSIZE; + goto fail; + } + memmove(buf + total, p, (uint)len); + total += len; + } + if (sock_domain(s) == AF_NETLINK) { + // sockaddr_nl: where it goes, by port id and/or multicast group. + uint dpid = 0, dgrp = 0; + if (m.msg_name && m.msg_namelen >= 12) { + if (copy_from_user(&dpid, (addr_t)m.msg_name + 4, 4) < 0 || + copy_from_user(&dgrp, (addr_t)m.msg_name + 8, 4) < 0) { + kfree(buf); + r = -EFAULT; + goto fail; + } + } else { + dgrp = UEVENT_GROUP_UDEV; + } + r = sock_uevent_send(s, buf, total, dpid, dgrp); + } else if (nf > 0) { + r = sockwrite_fds(s, buf, total, fs, nf); + if (r < 0) { + kfree(buf); + goto fail; // not queued: the descriptors are still ours + } + nf = 0; + } else { + r = sockwrite(s, buf, total); + } + kfree(buf); + if (r < 0) + goto fail; + return total; + } + for (i = 0; i < m.msg_iovlen; i++) { char *p; int len, w; @@ -1006,6 +1073,7 @@ SYSCALL_DEFINE(sendmsg) } if (len == 0) continue; + anydata = 1; if (addressed) w = socksendto(s, p, len, ip, port); @@ -1036,6 +1104,12 @@ SYSCALL_DEFINE(sendmsg) r = -EINVAL; goto fail; } + // A datagram with nothing in it is still a datagram: udev's workers say + // "done" with exactly that, and the credentials it carries are the message. + if (!anydata && sock_is_packet(s)) { + if ((r = sockwrite(s, "", 0)) < 0) + return r; + } return (int)sent; fail: @@ -1046,6 +1120,7 @@ fail: SYSCALL_DEFINE(recvmsg) { + int didread = 0; struct socket *s; struct file *file; struct msghdr m; @@ -1119,6 +1194,7 @@ SYSCALL_DEFINE(recvmsg) // one ended and the next began. if (got > 0 && (nf > 0 || !sockreadable(s))) break; + didread = 1; if (sock_domain(s) == AF_UNIX) rd = sockread_fds(s, p, len, fs, &nf); @@ -1137,6 +1213,19 @@ SYSCALL_DEFINE(recvmsg) break; // one message per call } + // A receive buffer of no bytes still takes a datagram off the queue (it is + // truncated to nothing). udev's workers report in with empty datagrams and + // read them this way; skipping the read left the message queued, the + // socket readable and the manager looping on it. + if (!didread && sock_is_packet(s)) { + char z; + int rd = sock_domain(s) == AF_UNIX + ? sockread_fds(s, &z, 0, fs, &nf) + : sockread(s, &z, 0); + if (rd < 0) + return rd; + } + if (nf > 0) { int room = 0, installed = 0, j; uint64 writelen; @@ -1197,7 +1286,7 @@ SYSCALL_DEFINE(recvmsg) // socket that is always the peer, so its recorded credentials are the // answer. WebKit sets the option on its IPC sockets and aborts if it is // refused. - if (sock_passcred(s) && got > 0) { + if (sock_passcred(s) && (got > 0 || sock_is_packet(s))) { struct { int pid; uint uid, gid; } cr; uint64 off = clen ? CMSG_SPACE(clen - CMSG_LEN(0)) : 0; if (sock_sendercred(s, &cr.pid, &cr.uid, &cr.gid) == 0) { @@ -1240,6 +1329,9 @@ SYSCALL_DEFINE(recvmsg) } else if (sock_domain(s) == AF_NETLINK) { // The kernel, as in recvfrom above. uint16 nl[6] = { AF_NETLINK, 0, 0, 0, 0, 0 }; + uint g = sock_nl_groups(s), from = sock_nl_from(s); + nl[2] = (uint16)from; nl[3] = (uint16)(from >> 16); + nl[4] = (uint16)g; nl[5] = (uint16)(g >> 16); alen = (uint32)sizeof(nl); if (alen > m.msg_namelen) alen = m.msg_namelen;