Compare commits

...
Author SHA1 Message Date
Nexoniarz
b3f72cd95e eudev starts at boot; Xorg finds its input devices through it
udevd and a one-shot trigger run from schinit, 10-schos.conf no longer lists
event0/event1, and the docs say what udev covers and what it does not.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
2026-10-26 11:44:21 +01:00
Nexoniarz
5d4d318ebf 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
2026-10-26 11:39:13 +01:00
Nexoniarz
a9f8cb7e0f 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
2026-10-26 09:51:41 +01:00
24 changed files with 1411 additions and 99 deletions

View file

@ -26,7 +26,7 @@ KERN_CORE = $(addprefix $(BUILD)/kernel/core/, \
KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \ 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 \ 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 \ 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 # Drivers for hardware that has to be *found* rather than assumed. Anything
# discovered by walking a bus belongs here; the fixed-address legacy devices # discovered by walking a bus belongs here; the fixed-address legacy devices

View file

@ -204,8 +204,8 @@ Another: **syscalls are declared in exactly one place**,
- **No GPU acceleration.** Xorg draws in software; the plan is Linux's `amdgpu` - **No GPU acceleration.** Xorg draws in software; the plan is Linux's `amdgpu`
through LDLS ([docs/plan/gpu.md](docs/plan/gpu.md)). There is no IOMMU either, through LDLS ([docs/plan/gpu.md](docs/plan/gpu.md)). There is no IOMMU either,
so a driver in a process is isolated against its bugs, not against malice. so a driver in a process is isolated against its bugs, not against malice.
- **No udev.** Xorg is told its input devices by a config file instead of - **udev sees the kernel's own devices only.** eudev runs and Xorg finds its input devices
finding them; `/sys` has no device tree and no uevent socket. through it; PCI and USB devices are not yet in the `/sys/devices` tree.
- **WebKit browsers do not start.** NetSurf does; Epiphany's web process dies at - **WebKit browsers do not start.** NetSurf does; Epiphany's web process dies at
start-up, and it has not been run to ground. start-up, and it has not been run to ground.
- **USB 2.0 controllers (EHCI)** have no driver. On older machines the ports - **USB 2.0 controllers (EHCI)** have no driver. On older machines the ports

View file

@ -94,6 +94,7 @@ Read in this order if you are new:
| [fat.md](fat.md) | FAT12/16/32 with long names: USB sticks, SD cards, the ESP. | | [fat.md](fat.md) | FAT12/16/32 with long names: USB sticks, SD cards, the ESP. |
| [devices.md](devices.md) | Buses, classes, binding, and the vtables consumers talk to. | | [devices.md](devices.md) | Buses, classes, binding, and the vtables consumers talk to. |
| [usb.md](usb.md) | xHCI, the USB core, and the three class drivers. | | [usb.md](usb.md) | xHCI, the USB core, and the three class drivers. |
| [udev.md](udev.md) | eudev on SchOS: the device table, the `/sys` tree, uevents, datagram sockets. |
| [graphics.md](graphics.md) | Framebuffer, `/dev/fb0`, text rendering, the console, PSOGM and GMMOS. | | [graphics.md](graphics.md) | Framebuffer, `/dev/fb0`, text rendering, the console, PSOGM and GMMOS. |
| [desktop.md](desktop.md) | Xorg on the framebuffer, Window Maker, and what they stand on. | | [desktop.md](desktop.md) | Xorg on the framebuffer, Window Maker, and what they stand on. |
| [packages.md](packages.md) | The apk root: Alpine's packages, and SchOS as one of them. | | [packages.md](packages.md) | The apk root: Alpine's packages, and SchOS as one of them. |

View file

@ -49,11 +49,9 @@ the real program is for.
`xterm` runs and exits, and the server stays up. `xterm` runs and exits, and the server stays up.
- **`/root/.xinitrc`** is `exec wmaker`. Without it xinit falls back to twm and an xterm, which - **`/root/.xinitrc`** is `exec wmaker`. Without it xinit falls back to twm and an xterm, which
are not in the image. are not in the image.
- **`/etc/X11/xorg.conf.d/10-schos.conf`** names the input devices. Alpine's Xorg asks udev for - **`/etc/X11/xorg.conf.d/10-schos.conf`** holds what the defaults do not give: Ctrl+Alt+Backspace
them, SchOS has no udev, and X came up with no keyboard and no mouse: the pointer did not enabled, the keyboard options, and the framebuffer driver. Input devices are *found*, not
move and nothing could end the session. SchOS has exactly two input nodes whatever the named: Xorg asks libudev, which reads what `udevd` wrote to `/run/udev` ([udev.md](udev.md)).
machine, so a fixed list is the whole answer (`AutoAddDevices` off, Ctrl+Alt+Backspace
enabled).
- **`/etc/asound.conf`** is for sound, not X ([audio.md](audio.md)). - **`/etc/asound.conf`** is for sound, not X ([audio.md](audio.md)).
## Using it ## Using it
@ -71,8 +69,9 @@ through ACPI, and reach X as a mouse: pointer, click and tap.
- **No acceleration.** Software rendering to the framebuffer: no DRM, no glamor, no DRI, no - **No acceleration.** Software rendering to the framebuffer: no DRM, no glamor, no DRI, no
OpenGL beyond what software gives. GPU drivers are a plan ([../plan/gpu.md](../plan/gpu.md)). OpenGL beyond what software gives. GPU drivers are a plan ([../plan/gpu.md](../plan/gpu.md)).
- **No udev.** Input devices are named in a config file, not found; `/sys` has no `devices` - **udev covers the kernel's own devices only.** Input, terminals, memory devices, the
tree and no uevent socket. framebuffer and disks are announced; PCI and USB devices are in `/sys/bus/*/devices` but are
not yet in the `/sys/devices` tree or on the uevent socket ([udev.md](udev.md)).
- **Touchpads are mice**: no two-finger scroll, no gestures, no palm rejection. - **Touchpads are mice**: no two-finger scroll, no gestures, no palm rejection.
- **No resolution change from inside X.** The mode is the console's; `schmode` changes the - **No resolution change from inside X.** The mode is the console's; `schmode` changes the
`vgpud` head only, and the loader's menu is where a real panel's mode is chosen. `vgpud` head only, and the loader's menu is where a real panel's mode is chosen.

View file

@ -275,10 +275,15 @@ at the moment it is read, so a stick shows up in `/sys/bus/usb/devices` as soon
as it is attached. Text files report size 0, as `/proc`'s do; binary ones (the as it is attached. Text files report size 0, as `/proc`'s do; binary ones (the
tables, `config`) report their real length, since tools copy them by it. tables, `config`) report their real length, since tools copy them by it.
What is missing, compared with Linux: `/sys/devices` and its symlinks (the The kernel's own devices (input, tty, mem, graphics, block) have the Linux tree:
`/sys/devices/virtual/<class>/<name>` with `dev`, `uevent` and a `subsystem`
link, `/sys/class/<class>/<name>` and `/sys/dev/{char,block}/MAJ:MIN` as symlinks
into it, and input capabilities; `uevent` is writable, and a write is announced
on the uevent netlink socket ([udev.md](udev.md)).
What is missing, compared with Linux: PCI and USB devices in that tree (the
`bus/*/devices` entries are directories rather than links into it), `bus/*/devices` entries are directories rather than links into it),
`/sys/class` beyond `dmi` and `power_supply`, `/sys/block`, a uevent socket (so `/sys/class` entries for them, `/sys/block`, and most of what is writable. PCI `config` is
no udev), and anything writable. PCI `config` is
256 bytes, because config space is read through port `0xCF8`, which reaches 256 bytes, because config space is read through port `0xCF8`, which reaches
no further. no further.

View file

@ -649,7 +649,11 @@ information was there and the code did not ask.
`SO_BROADCAST`, `TCP_NODELAY` reach lwIP's pcb; `SO_SNDBUF`/`SO_RCVBUF` report the `SO_BROADCAST`, `TCP_NODELAY` reach lwIP's pcb; `SO_SNDBUF`/`SO_RCVBUF` report the
real size; `SO_PASSCRED` on Unix sockets) and refuses the rest with `ENOPROTOOPT`. real size; `SO_PASSCRED` on Unix sockets) and refuses the rest with `ENOPROTOOPT`.
Notably absent: `SO_RCVTIMEO`/`SO_SNDTIMEO`. Notably absent: `SO_RCVTIMEO`/`SO_SNDTIMEO`.
- **`SOCK_DGRAM` over AF_UNIX does not exist** — stream only. - **`SOCK_DGRAM` and `SOCK_SEQPACKET` over AF_UNIX** keep message boundaries (each message carries a
16-byte header in the same ring) but have no unconnected form: `socketpair` and listeners work,
`sendto` a path does not.
- **Netlink `NETLINK_KOBJECT_UEVENT`** is the one multicast family: sockets bound to group 1 hear the
kernel's device events, group 2 what udevd re-announces, and a datagram can be addressed to a port id.
- **No IPv6**, no firewall or packet filter of any kind, and a TCP stack that has - **No IPv6**, no firewall or packet filter of any kind, and a TCP stack that has
been used for downloads at tens of megabits and no more than that. been used for downloads at tens of megabits and no more than that.
- **Three drivers in the kernel, one Linux driver outside it**, and the class is - **Three drivers in the kernel, one Linux driver outside it**, and the class is

82
docs/architecture/udev.md Normal file
View file

@ -0,0 +1,82 @@
# udev
Alpine's `eudev` runs on SchOS as an ordinary process, started by `schinit`
(`etc/schinit/udevd.job`, with `udev-trigger.job` replaying the devices that
were there first). It is what lets Xorg *find* its keyboard and mouse instead of
being told, and the road to a Wayland compositor starts here: libinput and
libseat both ask udev what exists.
The kernel's half is three pieces, all small, none of them specific to udev.
## The device table: `kernel/dev/kdev.c`
The devices the kernel implements itself — input event nodes, the memory
devices, terminals, the framebuffer, the disks — are listed once, with their
class, name, `/dev` name and parent. Everything below is generated from it.
The device numbers are the ones `stat()` reports for the node, not SchOS's
internal ones: `/dev/tty1` is `4:1` to a Linux program (`linux_tty_rdev` in
`kernel/sys/sys_fs.c`), and udev compares what it reads in `/sys` with what
`stat()` says about the node. `dev_stat_numbers()` is the one mapping both use.
## `/sys`: `kernel/fs/sysfs.c`
```
/sys/devices/virtual/input/input0/ name capabilities/{ev,key,rel,abs} properties
/sys/devices/virtual/input/input0/event0/ dev uevent subsystem -> ../../../../../class/input
/sys/class/input/event0 -> ../../devices/virtual/input/input0/event0
/sys/dev/char/13:64 ... -> ../../devices/virtual/...
```
Symlinks are real (`T_SYMLINK` inodes whose target is generated). `uevent` is
readable (`DEVPATH=`, `SUBSYSTEM=`, `MAJOR=`, `MINOR=`, `DEVNAME=`) and
writable: `echo add > uevent` — which is what `udevadm trigger` does — makes the
kernel announce the device again. A write to a PCI or USB device's `uevent` is
accepted and says nothing yet, because those devices are not in the
`/sys/devices` tree and an event's `DEVPATH` has to name something that exists.
The input capability files are what `input_id` reads to decide
`ID_INPUT_KEYBOARD` or `ID_INPUT_MOUSE`. SchOS has two input devices whatever
the machine — every keyboard on `event0`, every mouse on `event1`
(`kernel/dev/input.c`) — so each has a fixed, plausible capability set.
## `/dev`: `kernel/fs/devfs.c`
devtmpfs is no longer read-only: udev creates `/dev/char/MAJ:MIN`, `/dev/fd`,
`/dev/input/by-*` and the like. A small in-memory overlay (192 entries) holds
directories and symlinks, listed after the kernel's own nodes in whichever
directory they are in. Device nodes themselves are still the kernel's: `mknod`
and regular files in `/dev` are refused.
## The sockets: `kernel/net/unix.c`
- **`AF_UNIX` `SOCK_DGRAM` and `SOCK_SEQPACKET`** keep message boundaries.
udevd's control socket is `SEQPACKET`, and its manager and workers talk over
a `DGRAM` socketpair. A message carries its sender's credentials with it
(several workers share one end), a zero-length datagram is a datagram, and a
datagram socket never reads end-of-stream.
- **`AF_NETLINK` / `NETLINK_KOBJECT_UEVENT`** sockets bind to multicast groups:
group 1 is the kernel's events, group 2 is what udevd re-announces once its
rules have run. A datagram can also be addressed to a port id — udevd hands
an event to an idle worker that way, and the worker believes it only from the
manager's port. The kernel's messages arrive as credentials with uid 0, pid 0.
A queue that is full drops the message, as Linux does, and `udevadm trigger`
is how a reader recovers.
## What is not there
- PCI and USB devices in `/sys/devices`, and `add`/`remove` events from the
hotplug thread. A USB keyboard works because every keyboard is `event0`, not
because udev knew it arrived.
- `hwdb` (`/etc/udev/hwdb.bin`): `udevadm hwdb --update` has not been run in the
image, so `60-input-id` logs "returned non-zero" for the hwdb import. The
properties that matter come from `input_id`.
- Block device partitions, `by-id` / `by-uuid` links, and a `/sys/block`.
- `libinput` and `libseat` (next; see [../plan/roadmap.md](../plan/roadmap.md)).
## Testing
Boot the ISO and run `udevd --debug &`, then `udevadm trigger; udevadm settle`.
`/run/udev/data/c7:0` should contain
`ID_INPUT_KEYBOARD=1`, and `udevadm info /dev/input/event0` should print the
device. Under X, `/var/log/Xorg.0.log` says `config/udev: Adding input device`.

View file

@ -114,7 +114,7 @@ it lives.
| `mprotect` is a no-op, nothing has NX | [kernel.md](../architecture/kernel.md) | | `mprotect` is a no-op, nothing has NX | [kernel.md](../architecture/kernel.md) |
| No routing table | first interface wins, which is a rule and not routing — [networking.md](../architecture/networking.md) | | No routing table | first interface wins, which is a rule and not routing — [networking.md](../architecture/networking.md) |
| No descriptor garbage collector | a socket passed over itself and never received leaks the pair | | No descriptor garbage collector | a socket passed over itself and never received leaks the pair |
| No udev / uevent socket | Xorg is told its input devices — [desktop.md](../architecture/desktop.md) | | udev sees only the kernel's own devices | PCI and USB are not in the `/sys/devices` tree — [udev.md](../architecture/udev.md) |
| No EHCI driver | USB 2.0 ports wired only to EHCI do nothing — [usb.md](../architecture/usb.md) | | No EHCI driver | USB 2.0 ports wired only to EHCI do nothing — [usb.md](../architecture/usb.md) |
| Touchpads are mice | no two-finger scroll — [desktop.md](../architecture/desktop.md) | | Touchpads are mice | no two-finger scroll — [desktop.md](../architecture/desktop.md) |
| `ru_stime` is zero | [kernel.md](../architecture/kernel.md) | | `ru_stime` is zero | [kernel.md](../architecture/kernel.md) |

View file

@ -62,11 +62,10 @@ compositor worth running is years of work that Alpine already packages.) The des
Wayland compositor (labwc, sway, weston, a GNOME or KDE session) is the larger step. What Wayland — and a modern X — need Wayland compositor (labwc, sway, weston, a GNOME or KDE session) is the larger step. What Wayland — and a modern X — need
from the system, in the order they are useful: from the system, in the order they are useful:
1. **udev.** Today Xorg is *told* its input devices (`xorg.conf.d/10-schos.conf`) because nothing announces them. The fix is 1. **udev — done for the kernel's own devices** ([udev.md](../architecture/udev.md)). eudev runs from `schinit`, the uevent
Alpine's `eudev` (or `udev`) running on SchOS, which wants: a **uevent netlink socket** (`NETLINK_KOBJECT_UEVENT`, with the netlink socket and the `/sys/devices` tree exist, devtmpfs takes udev's links, and Xorg finds its keyboard and mouse
`add`/`remove`/`change` messages and their environment), a real **`/sys/devices` tree** with `/sys/bus/*/devices/*` and through it. **Left:** PCI and USB devices in the same tree, with `add`/`remove` events from the hotplug thread, so a USB
`/sys/class/*/*` as symlinks into it, `uevent` files that can be written to replay events (`udevadm trigger`), and devtmpfs keyboard or a GPU appears to udev; libinput then enumerates evdev devices through it.
that udev can create nodes in. libinput then enumerates evdev devices through it.
2. **libinput and libseat** (seatd is in Alpine): evdev with the properties libinput reads (`ID_INPUT_*` from udev), and a way 2. **libinput and libseat** (seatd is in Alpine): evdev with the properties libinput reads (`ID_INPUT_*` from udev), and a way
for a non-root compositor to take the keyboard and the display from the kernel — the `VT_PROCESS` handshake exists. for a non-root compositor to take the keyboard and the display from the kernel — the `VT_PROCESS` handshake exists.
3. **A DRM device.** Wayland compositors want `/dev/dri/card0` with KMS, and Mesa wants `renderD128`. SchOS has neither: it has 3. **A DRM device.** Wayland compositors want `/dev/dri/card0` with KMS, and Mesa wants `renderD128`. SchOS has neither: it has

View file

@ -0,0 +1,14 @@
# Replay the devices that were already there when udevd started, so /run/udev
# knows about them. Runs once; see /etc/udev-trigger.
#
# udevd is started by its own job and schinit has no ordering, so the script
# waits for the daemon's control socket.
Label = os.schos.udev-trigger
Program = /bin/sh
Args = sh /etc/udev-trigger
RunAtLoad = yes
KeepAlive = no
StandardOut = /dev/tty1
StandardError = /dev/tty1

16
etc/schinit/udevd.job Normal file
View file

@ -0,0 +1,16 @@
# eudev's daemon: the thing that turns the kernel's uevents into /run/udev
# data and /dev symlinks, which is how Xorg and every Wayland compositor learn
# what input and display devices exist.
#
# Alpine's eudev package; the kernel side is the uevent netlink socket and the
# /sys device tree (kernel/dev/kdev.c, kernel/fs/sysfs.c).
Label = os.schos.udevd
Program = /sbin/udevd
Args = udevd
RunAtLoad = yes
KeepAlive = yes
ThrottleInterval = 3
StandardOut = /dev/tty1
StandardError = /dev/tty1

9
etc/udev-trigger Normal file
View file

@ -0,0 +1,9 @@
#!/bin/sh
# Wait for udevd to listen, then ask the kernel to announce every device again.
i=0
while [ ! -S /run/udev/control ] && [ $i -lt 100 ]; do
sleep 0.1
i=$((i + 1))
done
udevadm trigger --action=add
udevadm settle --timeout=60

View file

@ -539,6 +539,16 @@ int socknl_copy(struct socket*, uint, char*, int);
void socknl_take(struct socket*, uint); void socknl_take(struct socket*, uint);
int sock_shutdown(struct socket*, int how); int sock_shutdown(struct socket*, int how);
int sockpair(struct socket **a, struct socket **b); 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);
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*); const char* sockname(struct socket*);
// AF_INET addressing. Separate from sockbind/sockconnect because the families // AF_INET addressing. Separate from sockbind/sockconnect because the families
// do not name the same kind of thing; `addr` and `port` are network order. // do not name the same kind of thing; `addr` and `port` are network order.

44
include/dev/kdev.h Normal file
View file

@ -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);

View file

@ -24,10 +24,14 @@
#define AF_NETLINK 16 #define AF_NETLINK 16
#define PF_NETLINK AF_NETLINK #define PF_NETLINK AF_NETLINK
#define NETLINK_ROUTE 0 #define NETLINK_ROUTE 0
#define NETLINK_KOBJECT_UEVENT 15
#define UEVENT_GROUP_KERNEL 1 // what the kernel announces
#define UEVENT_GROUP_UDEV 2 // what udevd re-announces after its rules
#define SOCK_STREAM 1 #define SOCK_STREAM 1
#define SOCK_DGRAM 2 #define SOCK_DGRAM 2
#define SOCK_RAW 3 #define SOCK_RAW 3
#define SOCK_SEQPACKET 5
// Flags ORed into socket()'s and accept4()'s `type`/`flags` argument. Linux's // Flags ORed into socket()'s and accept4()'s `type`/`flags` argument. Linux's
// values, because a musl program passes Linux's constants. // values, because a musl program passes Linux's constants.
@ -337,6 +341,15 @@ struct socket {
char data[SOCKBUF]; char data[SOCKBUF];
uint nread, nwrite; uint nread, nwrite;
// 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. // Connections waiting to be accepted, on a listener.
struct socket *queue[NBACKLOG]; struct socket *queue[NBACKLOG];
int nqueue; int nqueue;

240
kernel/dev/kdev.c Normal file
View file

@ -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);
}

View file

@ -39,6 +39,7 @@
#include "uapi/errno.h" #include "uapi/errno.h"
#include "fs/blkdev.h" #include "fs/blkdev.h"
#include "dev/pty.h" #include "dev/pty.h"
#include "core/uaccess.h"
// Inode numbering. Two directories and a flat table of nodes, so the number is // 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. // either a directory id or an index into devnodes[] offset past them.
@ -57,6 +58,52 @@
#define DEVFS_PTS0 8192ULL #define DEVFS_PTS0 8192ULL
#define DEVFS_PTM0 12288ULL #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 // 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 // tree rather than a flag, which is the point at which this file should grow
// one instead of adding DEV_DIR_THIRD. // one instead of adding DEV_DIR_THIRD.
@ -158,7 +205,21 @@ devfs_iload(struct inode *ip)
ip->major = ip->minor = 0; ip->major = ip->minor = 0;
ip->size = 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->inum == DEVFS_PTS) {
ip->type = T_DIR; ip->type = T_DIR;
ip->nlink = 2; ip->nlink = 2;
@ -193,16 +254,90 @@ devfs_iload(struct inode *ip)
} }
static void devfs_istore(struct inode *ip) { (void)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; } 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 // 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 // through devsw. Present because fs_ops has the slots, and answering -EIO is
// better than a null pointer if something ever does arrive here. // 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) static int
{ (void)ip; (void)u; (void)d; (void)o; (void)n; return -1; } devfs_read(struct inode *ip, int u, uint64 d, uint64 o, uint n)
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; } 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();
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_DIR)
return (int)n; // see devfs_read
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 static int
streq(const char *a, const char *b) streq(const char *a, const char *b)
@ -211,12 +346,41 @@ streq(const char *a, const char *b)
return *a == *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 * static struct inode *
devfs_dirlookup(struct inode *dp, char *name, uint *poff) devfs_dirlookup(struct inode *dp, char *name, uint *poff)
{ {
if (poff) if (poff)
*poff = 0; *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)) if (streq(name, ".") || (streq(name, "..") && dp->inum == DEVFS_ROOT))
return iget(DEVFS_DEV, dp->inum); return iget(DEVFS_DEV, dp->inum);
if (streq(name, "..")) if (streq(name, ".."))
@ -261,19 +425,85 @@ devfs_dirlookup(struct inode *dp, char *name, uint *poff)
if (streq(name, blkdev_name((uint)b))) if (streq(name, blkdev_name((uint)b)))
return iget(DEVFS_DEV, DEVFS_BLK0 + (uint64)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; return 0;
} }
// A read-only filesystem, and these say so rather than pretending. mknod on static int
// /dev returning EROFS is the correct answer: the node either exists because devfs_dirlink(struct inode *dp, char *name, uint64 inum)
// 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) int x = extra_of(inum);
{ (void)dp; (void)name; (void)inum; return -EROFS; } if (streq(name, ".") || streq(name, ".."))
static int devfs_dirunlink(struct inode *dp, char *name, uint off) return 0; /* a new directory's own dots */
{ (void)dp; (void)name; (void)off; return -EROFS; } if (x < 0 || strlen(name) >= EXTRA_NAME)
static int devfs_dirempty(struct inode *dp) { (void)dp; return 0; } return -EINVAL;
static struct inode *devfs_ialloc(uint dev, short type) extras_init();
{ (void)dev; (void)type; return 0; } 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 { struct linux_dirent64 {
uint64 d_ino; uint64 d_ino;
@ -312,8 +542,9 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
int used = 0; int used = 0;
uint64 n = *poff; uint64 n = *poff;
int isx = extra_of(dp->inum) >= 0;
if (dp->inum != DEVFS_ROOT && dp->inum != DEVFS_INPUT && if (dp->inum != DEVFS_ROOT && dp->inum != DEVFS_INPUT &&
dp->inum != DEVFS_PTS) dp->inum != DEVFS_PTS && !isx)
return -1; return -1;
if (n == 0) { if (n == 0) {
@ -326,6 +557,25 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
n++; 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, // /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 // whether or not the ones before it exist, so a pair opened between two
// calls does not shift the rest. // calls does not shift the rest.
@ -378,6 +628,21 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
base++; 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: out:
*poff = n; *poff = n;
return used; return used;

View file

@ -45,6 +45,7 @@
#include "core/hwinfo.h" #include "core/hwinfo.h"
#include "gfx/gfx.h" #include "gfx/gfx.h"
#include "dev/acpi.h" #include "dev/acpi.h"
#include "dev/kdev.h"
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Inode numbers: kind, index, file -- the whole identity, since iget gives // 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_DMIID 6ULL // file a DMI_* field + 1
#define K_DMITAB 7ULL // file 1 the entry point, 2 the table #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_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/<subsys>
#define K_KCLASS 11ULL // idx a kdev subsystem: /sys/class/<subsys>
#define K_KCLINK 12ULL // idx a kdev: the symlink in /sys/class/<subsys>
#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 INO(k, idx, f) (((uint64)(k) << 40) | ((uint64)(idx) << 8) | (uint64)(f))
#define I_KIND(i) ((int)((i) >> 40)) #define I_KIND(i) ((int)((i) >> 40))
@ -68,6 +79,7 @@
enum { enum {
D_ROOT, D_BUS, D_PCI, D_PCIDEV, D_USB, D_USBDEV, D_FW, D_ACPI, D_ACPITAB, 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_DMI, D_DMITAB, D_MEMMAP, D_EFI, D_CLASS, D_CLASSDMI, D_DMIID, D_PSU,
D_DEVICES, D_VIRTUAL, D_DEVT,
D_NDIRS D_NDIRS
}; };
@ -89,6 +101,9 @@ static const struct { const char *name; int parent; } dirs[D_NDIRS] = {
[D_CLASSDMI] = { "dmi", D_CLASS }, [D_CLASSDMI] = { "dmi", D_CLASS },
[D_DMIID] = { "id", D_CLASSDMI }, [D_DMIID] = { "id", D_CLASSDMI },
[D_PSU] = { "power_supply", D_CLASS }, [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) #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 static int
children(uint64 dino, child_fn cb, void *ctx) children(uint64 dino, child_fn cb, void *ctx)
{ {
@ -403,6 +508,21 @@ children(uint64 dino, child_fn cb, void *ctx)
return 1; return 1;
} }
switch (idx) { 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: case D_PCIDEV:
for (int i = 0; i < pci_count(); i++) { for (int i = 0; i < pci_count(); i++) {
struct out o = { name, 0, sizeof name - 1 }; 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++) for (int f = 1; f < NPSUFILES; f++)
if (psu_has(&b, f) && cb(ctx, INO(K_PSU, idx, f), 0, psufiles[f])) if (psu_has(&b, f) && cb(ctx, INO(K_PSU, idx, f), 0, psufiles[f]))
return 1; 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) { } else if (kind == K_MEMMAP && I_FILE(dino) == 0) {
for (int f = 1; f < 4; f++) for (int f = 1; f < 4; f++)
if (cb(ctx, INO(K_MEMMAP, idx, f), 0, memfiles[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_USB: return INO(K_DIR, D_USBDEV, 0);
case K_MEMMAP: return INO(K_DIR, D_MEMMAP, 0); case K_MEMMAP: return INO(K_DIR, D_MEMMAP, 0);
case K_PSU: return INO(K_DIR, D_PSU, 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; return SYSFS_ROOT;
} }
@ -687,6 +823,67 @@ text(uint64 ino, char *buf, int cap)
putc_(&o, '\n'); putc_(&o, '\n');
return 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/<s>.
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: { case K_DMIID: {
int n = dmi_field(f - 1, buf, cap - 1); int n = dmi_field(f - 1, buf, cap - 1);
if (n < 0) if (n < 0)
@ -722,10 +919,30 @@ exists_dir(uint64 ino)
} }
case K_PSU: case K_PSU:
return I_IDX(ino) < acpi_psu_count(); 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; 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 static void
sysfs_iload(struct inode *ip) sysfs_iload(struct inode *ip)
{ {
@ -740,13 +957,24 @@ sysfs_iload(struct inode *ip)
ip->size = 0; ip->size = 0;
ip->nlink = 1; ip->nlink = 1;
int subdir = I_KIND(ino) == K_PCI && int subdir = (I_KIND(ino) == K_PCI &&
(I_FILE(ino) == PCI_GRAPHICS || I_FILE(ino) == PCI_FB0); (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)) { if ((I_FILE(ino) == 0 || subdir) && exists_dir(ino)) {
ip->type = T_DIR; ip->type = T_DIR;
ip->nlink = 2; ip->nlink = 2;
return; 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); int n = binary(ino, 0, 0);
if (n >= 0) { if (n >= 0) {
ip->type = T_FILE; ip->type = T_FILE;
@ -761,7 +989,10 @@ sysfs_iload(struct inode *ip)
kmfree(buf); kmfree(buf);
if (n >= 0) { if (n >= 0) {
ip->type = T_FILE; 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; ip->rwmode = 3;
return; return;
} }
@ -822,9 +1053,31 @@ sysfs_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n)
static int static int
sysfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n) 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) if (I_KIND(ip->inum) == K_PCI && I_FILE(ip->inum) == PCI_ENABLE)
return (int)n; 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; return -EROFS;
} }
@ -896,7 +1149,7 @@ emit_cb(void *ctx, uint64 ino, int isdir, const char *name)
de->d_ino = ino; de->d_ino = ino;
de->d_off = l->used + rec; de->d_off = l->used + rec;
de->d_reclen = (ushort)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); memmove(de->d_name, name, nlen);
memset(de->d_name + nlen, 0, rec - (int)sizeof(*de) - nlen); memset(de->d_name + nlen, 0, rec - (int)sizeof(*de) - nlen);
l->used += rec; l->used += rec;

View file

@ -15,15 +15,18 @@
// is the property sleep/wakeup actually requires. Thirty-two sockets carrying // is the property sleep/wakeup actually requires. Thirty-two sockets carrying
// GUI messages will not notice the contention. // GUI messages will not notice the contention.
// //
// **Names are held in a table here, not in the filesystem.** A real Unix // **Names live in the filesystem.** bind() creates a socket node
// socket appears in a directory listing as a special file, and binding one // (kernel/sys/sys_net.c) and connect() resolves a path to the inode and looks
// creates that node. Doing that properly means a new inode type threaded // for the socket bound to that inode, so every spelling of a path arrives at
// through both filesystems, iget, dirlookup and stat -- a VFS change, not a // the same place and the node can be stat()ed and unlink()ed.
// socket change. The table gives every behaviour that matters: a client //
// finds a server by a name it agreed in advance, which is the whole job. // **Datagram and sequenced-packet sockets** share the ring with streams: each
// What is lost is `ls -l /tmp/schcom` showing an `s`, and `unlink()` of a // message is a 16-byte header -- length and multicast groups, sender port or
// socket path failing with ENOENT. Both are worth fixing when the VFS next // pid, sender uid and gid -- and then its bytes, so a read takes exactly one
// gets opened up; neither blocks anything. // message and credentials belong to the message, not to the socket. udev's
// workers share one end of a socketpair and the manager has to know which of
// them wrote what. The kernel's uevents travel the same way, to netlink
// sockets of protocol NETLINK_KOBJECT_UEVENT.
#include "core/types.h" #include "core/types.h"
#include "core/defs.h" #include "core/defs.h"
@ -113,6 +116,139 @@ sockinit(void)
"the socket table, with buffered bytes and descriptors in flight"); "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);
}
// 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 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;
}
// 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)
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)
continue;
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;
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. // Caller holds stab.lock.
static struct socket * static struct socket *
alloc_locked(int type) alloc_locked(int type)
@ -415,7 +551,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 // 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 // than that more is coming. Blocking here would hang a getifaddrs() that
// asked one question too many. // asked one question too many.
if(s->domain == AF_NETLINK){ if(s->domain == AF_NETLINK && !is_packet(s)){
acquire(&stab.lock); acquire(&stab.lock);
uint have = s->nwrite - s->nread; uint have = s->nwrite - s->nread;
if(have == 0){ if(have == 0){
@ -430,8 +566,10 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
return n; 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); 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){ if(myproc()->killed || s->state == SS_FREE){
release(&stab.lock); release(&stab.lock);
return -EINTR; return -EINTR;
@ -439,6 +577,51 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
sleep(&s->nread, &stab.lock); 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, 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++)
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 += 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];
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 // 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 // exactly on one -- and takes them -- or is cut short so that it stops
// before it, leaving the next read to start there. // before it, leaving the next read to start there.
@ -519,6 +702,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 // 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 // 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. // 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_dispatch(UEVENT_GROUP_UDEV, 0, addr, n, s);
return n;
}
if(s->domain == AF_NETLINK){ if(s->domain == AF_NETLINK){
acquire(&stab.lock); acquire(&stab.lock);
s->nread = s->nwrite = 0; s->nread = s->nwrite = 0;
@ -548,6 +736,49 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
return -ENOBUFS; return -ENOBUFS;
} }
if(is_packet(s)){
if(n + PKTHDR > 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 + PKTHDR <= 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];
}
// 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();
return n;
}
for(i = 0; i < n; i++){ for(i = 0; i < n; i++){
// Re-read every iteration: the peer can go away while we sleep below. // Re-read every iteration: the peer can go away while we sleep below.
dst = s->peer; dst = s->peer;
@ -607,9 +838,11 @@ socknew(int domain, int type, int proto)
if(domain != AF_UNIX && domain != AF_INET && domain != AF_NETLINK) if(domain != AF_UNIX && domain != AF_INET && domain != AF_NETLINK)
return 0; 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. // 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; return 0;
// AF_NETLINK carries whole messages and musl asks for SOCK_RAW; SOCK_DGRAM is // 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 // accepted too because Linux does. NETLINK_ROUTE is the only protocol served
@ -618,14 +851,16 @@ socknew(int domain, int type, int proto)
if(domain == AF_NETLINK){ if(domain == AF_NETLINK){
if(type != SOCK_RAW && type != SOCK_DGRAM) if(type != SOCK_RAW && type != SOCK_DGRAM)
return 0; return 0;
if(proto != NETLINK_ROUTE) if(proto != NETLINK_ROUTE && proto != NETLINK_KOBJECT_UEVENT)
return 0; return 0;
} }
acquire(&stab.lock); acquire(&stab.lock);
s = alloc_locked(type); s = alloc_locked(type);
if(s) if(s){
s->domain = domain; s->domain = domain;
s->nlproto = domain == AF_NETLINK ? proto : 0;
}
release(&stab.lock); release(&stab.lock);
if(s == 0) if(s == 0)
return 0; return 0;
@ -644,16 +879,16 @@ socknew(int domain, int type, int proto)
// A connected pair with no name and no listener, for socketpair(2). // A connected pair with no name and no listener, for socketpair(2).
int int
sockpair(struct socket **pa, struct socket **pb) sockpair_type(struct socket **pa, struct socket **pb, int type)
{ {
struct socket *a, *b; struct socket *a, *b;
acquire(&stab.lock); acquire(&stab.lock);
if((a = alloc_locked(SOCK_STREAM)) == 0){ if((a = alloc_locked(type)) == 0){
release(&stab.lock); release(&stab.lock);
return -ENFILE; return -ENFILE;
} }
if((b = alloc_locked(SOCK_STREAM)) == 0){ if((b = alloc_locked(type)) == 0){
a->state = SS_FREE; a->state = SS_FREE;
a->ref = 0; a->ref = 0;
release(&stab.lock); release(&stab.lock);
@ -674,6 +909,12 @@ sockpair(struct socket **pa, struct socket **pb)
return 0; 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 // 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 // hands over its identity; see sys_bind() in kernel/sys/sys_net.c for why the
// creation lives there and not here. // creation lives there and not here.
@ -972,7 +1213,7 @@ sockreadable(struct socket *s)
return inet_readable(s->inet); return inet_readable(s->inet);
if(s->state == SS_LISTENING) if(s->state == SS_LISTENING)
return s->nqueue > 0; 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. // The peer is gone and everything it sent has been read.
@ -1091,6 +1332,19 @@ sock_sendercred(struct socket *s, int *pid, uint *uid, uint *gid)
{ {
int r = -1; 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); acquire(&stab.lock);
if(s->domain != AF_INET && s->peer){ if(s->domain != AF_INET && s->peer){
struct socket *p = s->peer; struct socket *p = s->peer;

View file

@ -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; 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 // Internal helpers
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------

View file

@ -24,6 +24,7 @@
#include "uapi/posix.h" #include "uapi/posix.h"
#include "core/syscall.h" #include "core/syscall.h"
#include "core/uaccess.h" #include "core/uaccess.h"
#include "core/kmem.h"
#include "uapi/errno.h" #include "uapi/errno.h"
#include "net/socket.h" #include "net/socket.h"
@ -164,7 +165,7 @@ SYSCALL_DEFINE(socket)
if (type & ~(0xFF | SOCK_CLOEXEC | SOCK_NONBLOCK)) if (type & ~(0xFF | SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL; return -EINVAL;
type &= 0xFF; type &= 0xFF;
if (domain == AF_UNIX && type != SOCK_STREAM) if (domain == AF_UNIX && type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET)
return -EPROTOTYPE; return -EPROTOTYPE;
if (domain == AF_INET && type != SOCK_STREAM && type != SOCK_DGRAM && if (domain == AF_INET && type != SOCK_STREAM && type != SOCK_DGRAM &&
type != SOCK_RAW) type != SOCK_RAW)
@ -185,7 +186,7 @@ SYSCALL_DEFINE(socket)
// default rather than departing from it, and programs do write them out: // default rather than departing from it, and programs do write them out:
// toybox's `nc` passes IPPROTO_TCP, and refusing it meant "Protocol not // toybox's `nc` passes IPPROTO_TCP, and refusing it meant "Protocol not
// supported" for TCP on a system whose whole network is TCP. // supported" for TCP on a system whose whole network is TCP.
if (protocol != 0) { if (protocol != 0 && domain != AF_NETLINK) {
int redundant = int redundant =
(domain == AF_INET && type == SOCK_STREAM && protocol == IPPROTO_TCP) || (domain == AF_INET && type == SOCK_STREAM && protocol == IPPROTO_TCP) ||
(domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP); (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP);
@ -270,6 +271,17 @@ SYSCALL_DEFINE(bind)
return sockbind_in(s, ip, port_from_net(port)); return sockbind_in(s, ip, port_from_net(port));
} }
// sockaddr_nl: family, pad, port id, groups. Only the groups matter here.
if (sock_domain(s) == AF_NETLINK) {
uint pid = 0, groups = 0;
if (ulen < 12)
return -EINVAL;
if (copy_from_user(&pid, uaddr + 4, 4) < 0 ||
copy_from_user(&groups, uaddr + 8, 4) < 0)
return -EFAULT;
return sock_netlink_bind(s, pid, groups);
}
uint64 key; uint64 key;
if (sun_abstract(uaddr, ulen, &key)) if (sun_abstract(uaddr, ulen, &key))
return sockbind_abstract(s, key); return sockbind_abstract(s, key);
@ -459,15 +471,11 @@ SYSCALL_DEFINE(socketpair)
return -EAFNOSUPPORT; return -EAFNOSUPPORT;
int sflags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK); int sflags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
type &= 0xFF; type &= 0xFF;
// Datagram and sequenced-packet pairs are carried as a stream. What they // Datagram and sequenced-packet pairs keep message boundaries (kernel/net/unix.c).
// are used for between two processes is passing a descriptor or a status if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET)
// word -- Rust's process spawning asks for SOCK_SEQPACKET to hear back
// from its child -- and one message at a time is all of that. A reader
// that sends two and wants them kept apart would see them joined.
if (type != SOCK_STREAM && type != 2 /* DGRAM */ && type != 5 /* SEQPACKET */)
return -EPROTOTYPE; return -EPROTOTYPE;
if ((r = sockpair(&a, &b)) < 0) if ((r = sockpair_type(&a, &b, type)) < 0)
return r; return r;
if ((fd0 = sock_to_fd(a, sflags)) < 0) { if ((fd0 = sock_to_fd(a, sflags)) < 0) {
@ -602,6 +610,9 @@ SYSCALL_DEFINE(recvfrom)
// believes a message, and with no address at all it hit an assertion // believes a message, and with no address at all it hit an assertion
// and every GNOME program that watches the network aborted. // and every GNOME program that watches the network aborted.
uint16 nl[6] = { AF_NETLINK, 0, 0, 0, 0, 0 }; 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); alen = (int)sizeof(nl);
int cap = alen; int cap = alen;
if (ulenp && get_user(cap, ulenp) < 0) if (ulenp && get_user(cap, ulenp) < 0)
@ -660,6 +671,12 @@ sock_name(int peer)
// A sockaddr_nl of zeros: family, padding, port id 0 (the kernel), // A sockaddr_nl of zeros: family, padding, port id 0 (the kernel),
// no groups. // no groups.
sa.un.sun_family = AF_NETLINK; 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; alen = 12;
} else { } else {
if ((r = sockname_unix(s, peer, sa.un.sun_path)) < 0) if ((r = sockname_unix(s, peer, sa.un.sun_path)) < 0)
@ -965,6 +982,7 @@ SYSCALL_DEFINE(sendmsg)
uint ip = 0; uint ip = 0;
ushort port = 0; ushort port = 0;
int addressed = 0; int addressed = 0;
int anydata = 0;
if ((r = argsockfile(0, &s, &file)) < 0) if ((r = argsockfile(0, &s, &file)) < 0)
return r; return r;
@ -990,6 +1008,61 @@ SYSCALL_DEFINE(sendmsg)
addressed = 1; 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++) { for (i = 0; i < m.msg_iovlen; i++) {
char *p; char *p;
int len, w; int len, w;
@ -1000,6 +1073,7 @@ SYSCALL_DEFINE(sendmsg)
} }
if (len == 0) if (len == 0)
continue; continue;
anydata = 1;
if (addressed) if (addressed)
w = socksendto(s, p, len, ip, port); w = socksendto(s, p, len, ip, port);
@ -1030,6 +1104,12 @@ SYSCALL_DEFINE(sendmsg)
r = -EINVAL; r = -EINVAL;
goto fail; 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; return (int)sent;
fail: fail:
@ -1040,6 +1120,7 @@ fail:
SYSCALL_DEFINE(recvmsg) SYSCALL_DEFINE(recvmsg)
{ {
int didread = 0;
struct socket *s; struct socket *s;
struct file *file; struct file *file;
struct msghdr m; struct msghdr m;
@ -1076,7 +1157,7 @@ SYSCALL_DEFINE(recvmsg)
// one rather than discarding it as Linux would: musl's getifaddrs reads // one rather than discarding it as Linux would: musl's getifaddrs reads
// in chunks, and this whole queue is one "datagram" only by convention. // in chunks, and this whole queue is one "datagram" only by convention.
int nlret = -1; int nlret = -1;
if (sock_domain(s) == AF_NETLINK) { if (sock_domain(s) == AF_NETLINK && !sock_is_packet(s)) {
uint have = socknl_pending(s); uint have = socknl_pending(s);
if (have == 0) if (have == 0)
return -EAGAIN; // never blocks; see sockread return -EAGAIN; // never blocks; see sockread
@ -1113,6 +1194,7 @@ SYSCALL_DEFINE(recvmsg)
// one ended and the next began. // one ended and the next began.
if (got > 0 && (nf > 0 || !sockreadable(s))) if (got > 0 && (nf > 0 || !sockreadable(s)))
break; break;
didread = 1;
if (sock_domain(s) == AF_UNIX) if (sock_domain(s) == AF_UNIX)
rd = sockread_fds(s, p, len, fs, &nf); rd = sockread_fds(s, p, len, fs, &nf);
@ -1127,8 +1209,21 @@ SYSCALL_DEFINE(recvmsg)
return rd; return rd;
} }
got += rd; got += rd;
if (rd < len) if (rd < len || sock_is_packet(s))
break; 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) { if (nf > 0) {
@ -1191,7 +1286,7 @@ SYSCALL_DEFINE(recvmsg)
// socket that is always the peer, so its recorded credentials are the // 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 // answer. WebKit sets the option on its IPC sockets and aborts if it is
// refused. // refused.
if (sock_passcred(s) && got > 0) { if (sock_passcred(s) && (got > 0 || sock_is_packet(s))) {
struct { int pid; uint uid, gid; } cr; struct { int pid; uint uid, gid; } cr;
uint64 off = clen ? CMSG_SPACE(clen - CMSG_LEN(0)) : 0; uint64 off = clen ? CMSG_SPACE(clen - CMSG_LEN(0)) : 0;
if (sock_sendercred(s, &cr.pid, &cr.uid, &cr.gid) == 0) { if (sock_sendercred(s, &cr.pid, &cr.uid, &cr.gid) == 0) {
@ -1234,6 +1329,9 @@ SYSCALL_DEFINE(recvmsg)
} else if (sock_domain(s) == AF_NETLINK) { } else if (sock_domain(s) == AF_NETLINK) {
// The kernel, as in recvfrom above. // The kernel, as in recvfrom above.
uint16 nl[6] = { AF_NETLINK, 0, 0, 0, 0, 0 }; 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); alen = (uint32)sizeof(nl);
if (alen > m.msg_namelen) if (alen > m.msg_namelen)
alen = m.msg_namelen; alen = m.msg_namelen;

View file

@ -56,3 +56,9 @@ curl
# not ALSA's kernel side, and SDL 3 has no OSS backend of its own. # not ALSA's kernel side, and SDL 3 has no OSS backend of its own.
alsa-lib alsa-lib
alsa-plugins alsa-plugins
# udev: the daemon that names devices and tells Xorg (and libinput, and every
# Wayland compositor) what is plugged in. eudev is udev without systemd.
# `udevadm`, the rules, and libudev come with it.
eudev
eudev-libs
udev-init-scripts

View file

@ -19,11 +19,12 @@ cairo-gobject-1.18.4-r1.apk ec302c75a24edaa4a2f867cfb5eec85cf30efe184044bab6e2b9
c-ares-1.34.9-r0.apk 3539a3e11c1ef1516117d8179a546ac882086e94e0d78ad51d687b3fa2b3b5f2 main c-ares-1.34.9-r0.apk 3539a3e11c1ef1516117d8179a546ac882086e94e0d78ad51d687b3fa2b3b5f2 main
cmatrix-2.0-r2.apk d9f6bea3851492a6db2a9cd3f7e2df0f0756c6651a702b36ed2b887328ad1f00 community cmatrix-2.0-r2.apk d9f6bea3851492a6db2a9cd3f7e2df0f0756c6651a702b36ed2b887328ad1f00 community
cups-libs-2.4.19-r1.apk 5739c568d90f23006d74ff82619e1a18447a6f698ae2af61970debe6658a7bea main cups-libs-2.4.19-r1.apk 5739c568d90f23006d74ff82619e1a18447a6f698ae2af61970debe6658a7bea main
curl-8.22.0-r0.apk 9262352e6341bb87e3047d1ca0b4051b2a9f4011409df25e4190e6be0334b860 main curl-8.23.0-r0.apk 531ae298451a4c1c44f563fc3b2eedc2c1d7e0a26a33bdfc0efe6faaff6a5942 main
curl-zsh-completion-8.22.0-r0.apk 182a5f0afb5fd8786137f4610c065c305aa593fd18994df908c146335fb6011c main curl-zsh-completion-8.23.0-r0.apk 123ff531444fe6c3095c49553f8798bcf6f6ca8c7668b4140cc33daf24c351ec main
dbus-libs-1.16.2-r2.apk 105f6ba17454c587ca9345f9deb348012d1950588a22da42120c954d396c99b6 main dbus-libs-1.16.2-r2.apk 105f6ba17454c587ca9345f9deb348012d1950588a22da42120c954d396c99b6 main
e2fsprogs-libs-1.47.4-r0.apk 2e067395a6981b06cfd2cd3abf0e6c6574f7ef64d02e01f75fc5323d4a337fb9 main e2fsprogs-libs-1.47.4-r0.apk 2e067395a6981b06cfd2cd3abf0e6c6574f7ef64d02e01f75fc5323d4a337fb9 main
encodings-1.1.0-r0.apk f7c0eb762638d0249b4fbbe07a8e4ed6aab8e86b74c239c40e47a36219777d52 main encodings-1.1.0-r0.apk f7c0eb762638d0249b4fbbe07a8e4ed6aab8e86b74c239c40e47a36219777d52 main
eudev-3.2.14-r6.apk 540c78af1eed3bc411cae4e9171e911afc0704210c0b97315e978e05c3b39dce main
eudev-libs-3.2.14-r6.apk 45fdbcf37c5c0e82067e98a818d3c1fd16d0261c62128dcb6b935b86b4ec6c86 main eudev-libs-3.2.14-r6.apk 45fdbcf37c5c0e82067e98a818d3c1fd16d0261c62128dcb6b935b86b4ec6c86 main
fastfetch-2.64.2-r0.apk 9c593d689f1c04625d3b433066ad92e430a384fa8e99585fee71310a20a965cb community fastfetch-2.64.2-r0.apk 9c593d689f1c04625d3b433066ad92e430a384fa8e99585fee71310a20a965cb community
fastfetch-zsh-completion-2.64.2-r0.apk ded7a5c7911c4db60ab71a320104f9af3f629976429c897db67ea87c5ac53ec8 community fastfetch-zsh-completion-2.64.2-r0.apk ded7a5c7911c4db60ab71a320104f9af3f629976429c897db67ea87c5ac53ec8 community
@ -48,6 +49,7 @@ harfbuzz-13.2.1-r0.apk 01f7862265ef2256bbadd0fc998df3d1243dee606df3bd8284471cf39
htop-3.5.3-r0.apk 6f6d444ca9a4133c2c13323ded04128783d59f924bcff0276d150a982904a2f3 main htop-3.5.3-r0.apk 6f6d444ca9a4133c2c13323ded04128783d59f924bcff0276d150a982904a2f3 main
hwdata-pci-0.408-r0.apk fe61404e6d2bd1e04c021d53641c0ebce97911084d77994e43a288a1278686c1 main hwdata-pci-0.408-r0.apk fe61404e6d2bd1e04c021d53641c0ebce97911084d77994e43a288a1278686c1 main
hwdata-usb-0.408-r0.apk 272d365cb05dd085dd8022154cf71e3f3b0aaddd41c1269eeaf939c9d38675c8 main hwdata-usb-0.408-r0.apk 272d365cb05dd085dd8022154cf71e3f3b0aaddd41c1269eeaf939c9d38675c8 main
kmod-libs-34.2-r1.apk d6504eb204a47a35e1dd5eaa1018301b2dc0b1809f84d88fab2fdbadfc9c3c73 main
lbreakouthd-1.2.2-r0.apk 4d7f78d866a7c544132e5f159d18f978515ac948517f2b8a6c625756bb81a3e0 community lbreakouthd-1.2.2-r0.apk 4d7f78d866a7c544132e5f159d18f978515ac948517f2b8a6c625756bb81a3e0 community
lbreakouthd-data-1.2.2-r0.apk 28ce98df27bfbb442fa397fbb9eb3eb5464e5b7b040ea034f207be52f4f4f07f community lbreakouthd-data-1.2.2-r0.apk 28ce98df27bfbb442fa397fbb9eb3eb5464e5b7b040ea034f207be52f4f4f07f community
lcms2-2.19-r0.apk b4cb038af3c2f02bedd8456ae876c97ac0a9f29789add77871bfd6ce084ff136 main lcms2-2.19-r0.apk b4cb038af3c2f02bedd8456ae876c97ac0a9f29789add77871bfd6ce084ff136 main
@ -59,7 +61,7 @@ libbz2-1.0.8-r6.apk 2737e2b23ed08d4911ee62cd0163b5f4bf0379cacf4dc35b23a00209584c
libcap2-2.78-r0.apk 42886a7b4bcde26347c712925c8dc7db202b6029ef9b8c7ee81577ab7bdd8fad main libcap2-2.78-r0.apk 42886a7b4bcde26347c712925c8dc7db202b6029ef9b8c7ee81577ab7bdd8fad main
libcom_err-1.47.4-r0.apk 4723d0fb8e286dc6adb54dd3cdaedcd8db54260f75ee778a5acb4c16922c0f52 main libcom_err-1.47.4-r0.apk 4723d0fb8e286dc6adb54dd3cdaedcd8db54260f75ee778a5acb4c16922c0f52 main
libcrypto3-3.5.9-r0.apk 6632d758d8f5e9ea3b650fe966f23bbf9a202f8b8dceecac93da135dec5e3689 main libcrypto3-3.5.9-r0.apk 6632d758d8f5e9ea3b650fe966f23bbf9a202f8b8dceecac93da135dec5e3689 main
libcurl-8.22.0-r0.apk 40b2a8e15a8d24bc95a5f42aaeb018313a1ff457cee71d8e15aaf1328da765e8 main libcurl-8.23.0-r0.apk 1f70ca404818805a6465b615bbfe84ef861488a963d7e630108ebd7fbaa51665 main
libdav1d-1.5.3-r0.apk 40a2b1b92cdcc6fd295934f799ee80ef67e2e384097624d565c6222d827f8480 main libdav1d-1.5.3-r0.apk 40a2b1b92cdcc6fd295934f799ee80ef67e2e384097624d565c6222d827f8480 main
libdrm-2.4.134-r0.apk 584fd84ad5030040e613fa1816097cd1706ddd542100070852b0eab6e09e12ca main libdrm-2.4.134-r0.apk 584fd84ad5030040e613fa1816097cd1706ddd542100070852b0eab6e09e12ca main
libeconf-0.8.3-r0.apk 2a791c7957961f830cf31b04f35f814a63afcf633badc9052425ff1cf2f64380 main libeconf-0.8.3-r0.apk 2a791c7957961f830cf31b04f35f814a63afcf633badc9052425ff1cf2f64380 main
@ -157,6 +159,7 @@ ssl_client-1.37.0-r31.apk e578a140670e4cf9816117b0c2063632bf3754ecbe8fa593a7fd2b
tiff-4.7.2-r0.apk 757ce87ebe4923a6be9869d026e250958ca09696529c464781e8a91d2b81670b main tiff-4.7.2-r0.apk 757ce87ebe4923a6be9869d026e250958ca09696529c464781e8a91d2b81670b main
tmux-3.7c-r0.apk 16f3438f5a24124dd500f70bf6f2a232c9b3dfe5e7f7e615969ac5c00c2c8d0b main tmux-3.7c-r0.apk 16f3438f5a24124dd500f70bf6f2a232c9b3dfe5e7f7e615969ac5c00c2c8d0b main
tmux-zsh-completion-5.9-r7.apk e505b2a8c0d56beb65b6f47fd732f6ee6d8d8a72a1a1ea27a6922dad48d9ce89 main tmux-zsh-completion-5.9-r7.apk e505b2a8c0d56beb65b6f47fd732f6ee6d8d8a72a1a1ea27a6922dad48d9ce89 main
udev-init-scripts-35-r1.apk b707bc50e5c8d2faec08f90e7603b76c2c42dfb0e90e713f82be0f98411d8161 main
util-macros-1.20.2-r0.apk 7c129d085b646b13ccef3738e93038221aec840c1a6764279c5adc9950ba1bdc main util-macros-1.20.2-r0.apk 7c129d085b646b13ccef3738e93038221aec840c1a6764279c5adc9950ba1bdc main
wayland-libs-client-1.25.0-r0.apk a728f7103217cafdc83ff6ad0d679c197dbbe280bd5788c8336259f7a39cb303 main wayland-libs-client-1.25.0-r0.apk a728f7103217cafdc83ff6ad0d679c197dbbe280bd5788c8336259f7a39cb303 main
wayland-libs-cursor-1.25.0-r0.apk 16ec369226507abaeb84b496ce2bb1a4ef29987ea91ba6bedc40bb0e0af274e7 main wayland-libs-cursor-1.25.0-r0.apk 16ec369226507abaeb84b496ce2bb1a4ef29987ea91ba6bedc40bb0e0af274e7 main

View file

@ -121,37 +121,26 @@ cat > "$P/root/.xinitrc" <<'XEOF'
exec wmaker exec wmaker
XEOF XEOF
# X's input devices, named rather than found. Alpine's Xorg asks udev for # X's input devices are found, not named: udevd (etc/schinit/udevd.job) turns
# them, SchOS has no udev, and X came up with no keyboard and no mouse: the # the kernel's uevents into /run/udev data, and Xorg asks libudev for every
# pointer did not move, Ctrl+Alt+F-keys did nothing and nothing could end the # device with ID_INPUT set. That was a hand-written list of event0 and event1
# session. SchOS has exactly two input nodes whatever the machine -- every # while SchOS had no udev. The file is SchOS's, in xorg.conf.d, which no Alpine
# keyboard on event0, every mouse on event1 (kernel/dev/input.c) -- so a # package writes into; what is left here is the options the defaults do not
# fixed list is the whole answer. The file is SchOS's, in xorg.conf.d, which # give, and the framebuffer driver.
# no Alpine package writes into.
mkdir -p "$P/etc/X11/xorg.conf.d" mkdir -p "$P/etc/X11/xorg.conf.d"
cat > "$P/etc/X11/xorg.conf.d/10-schos.conf" <<'XEOF' cat > "$P/etc/X11/xorg.conf.d/10-schos.conf" <<'XEOF'
# SchOS has no udev, so X is told its input devices instead of finding them.
# event0 is every keyboard and event1 every mouse (kernel/dev/input.c).
Section "ServerFlags" Section "ServerFlags"
Option "AutoAddDevices" "false"
# Ctrl+Alt+Backspace ends X. # Ctrl+Alt+Backspace ends X.
Option "DontZap" "false" Option "DontZap" "false"
EndSection EndSection
Section "InputDevice" Section "InputClass"
Identifier "keyboard" Identifier "SchOS keyboards"
Driver "evdev" MatchIsKeyboard "on"
Option "Device" "/dev/input/event0"
Option "XkbRules" "evdev" Option "XkbRules" "evdev"
Option "XkbOptions" "terminate:ctrl_alt_bksp" Option "XkbOptions" "terminate:ctrl_alt_bksp"
EndSection EndSection
Section "InputDevice"
Identifier "mouse"
Driver "evdev"
Option "Device" "/dev/input/event1"
EndSection
Section "Device" Section "Device"
Identifier "fb" Identifier "fb"
Driver "fbdev" Driver "fbdev"
@ -165,8 +154,6 @@ EndSection
Section "ServerLayout" Section "ServerLayout"
Identifier "schos" Identifier "schos"
Screen "screen" Screen "screen"
InputDevice "keyboard" "CoreKeyboard"
InputDevice "mouse" "CorePointer"
EndSection EndSection
XEOF XEOF