Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3f72cd95e | ||
|
|
5d4d318ebf | ||
|
|
a9f8cb7e0f |
24 changed files with 1411 additions and 99 deletions
2
Makefile
2
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
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
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.
|
||||
- **No udev.** Xorg is told its input devices by a config file instead of
|
||||
finding them; `/sys` has no device tree and no uevent socket.
|
||||
- **udev sees the kernel's own devices only.** eudev runs and Xorg finds its input devices
|
||||
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
|
||||
start-up, and it has not been run to ground.
|
||||
- **USB 2.0 controllers (EHCI)** have no driver. On older machines the ports
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
| [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. |
|
||||
| [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. |
|
||||
| [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. |
|
||||
|
|
|
|||
|
|
@ -49,11 +49,9 @@ the real program is for.
|
|||
`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
|
||||
are not in the image.
|
||||
- **`/etc/X11/xorg.conf.d/10-schos.conf`** names the input devices. Alpine's Xorg asks udev for
|
||||
them, SchOS has no udev, and X came up with no keyboard and no mouse: the pointer did not
|
||||
move and nothing could end the session. SchOS has exactly two input nodes whatever the
|
||||
machine, so a fixed list is the whole answer (`AutoAddDevices` off, Ctrl+Alt+Backspace
|
||||
enabled).
|
||||
- **`/etc/X11/xorg.conf.d/10-schos.conf`** holds what the defaults do not give: Ctrl+Alt+Backspace
|
||||
enabled, the keyboard options, and the framebuffer driver. Input devices are *found*, not
|
||||
named: Xorg asks libudev, which reads what `udevd` wrote to `/run/udev` ([udev.md](udev.md)).
|
||||
- **`/etc/asound.conf`** is for sound, not X ([audio.md](audio.md)).
|
||||
|
||||
## 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
|
||||
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`
|
||||
tree and no uevent socket.
|
||||
- **udev covers the kernel's own devices only.** Input, terminals, memory devices, the
|
||||
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.
|
||||
- **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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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),
|
||||
`/sys/class` beyond `dmi` and `power_supply`, `/sys/block`, a uevent socket (so
|
||||
no udev), and anything writable. PCI `config` is
|
||||
`/sys/class` entries for them, `/sys/block`, and most of what is writable. PCI `config` is
|
||||
256 bytes, because config space is read through port `0xCF8`, which reaches
|
||||
no further.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
real size; `SO_PASSCRED` on Unix sockets) and refuses the rest with `ENOPROTOOPT`.
|
||||
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
|
||||
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
|
||||
|
|
|
|||
82
docs/architecture/udev.md
Normal file
82
docs/architecture/udev.md
Normal 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`.
|
||||
|
|
@ -114,7 +114,7 @@ it lives.
|
|||
| `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 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) |
|
||||
| Touchpads are mice | no two-finger scroll — [desktop.md](../architecture/desktop.md) |
|
||||
| `ru_stime` is zero | [kernel.md](../architecture/kernel.md) |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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
|
||||
Alpine's `eudev` (or `udev`) running on SchOS, which wants: a **uevent netlink socket** (`NETLINK_KOBJECT_UEVENT`, with the
|
||||
`add`/`remove`/`change` messages and their environment), a real **`/sys/devices` tree** with `/sys/bus/*/devices/*` and
|
||||
`/sys/class/*/*` as symlinks into it, `uevent` files that can be written to replay events (`udevadm trigger`), and devtmpfs
|
||||
that udev can create nodes in. libinput then enumerates evdev devices through it.
|
||||
1. **udev — done for the kernel's own devices** ([udev.md](../architecture/udev.md)). eudev runs from `schinit`, the uevent
|
||||
netlink socket and the `/sys/devices` tree exist, devtmpfs takes udev's links, and Xorg finds its keyboard and mouse
|
||||
through it. **Left:** PCI and USB devices in the same tree, with `add`/`remove` events from the hotplug thread, so a USB
|
||||
keyboard or a GPU appears to udev; 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
|
||||
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
|
||||
|
|
|
|||
14
etc/schinit/udev-trigger.job
Normal file
14
etc/schinit/udev-trigger.job
Normal 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
16
etc/schinit/udevd.job
Normal 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
9
etc/udev-trigger
Normal 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
|
||||
|
|
@ -539,6 +539,16 @@ int socknl_copy(struct socket*, uint, char*, int);
|
|||
void socknl_take(struct socket*, uint);
|
||||
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);
|
||||
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
|
||||
// do not name the same kind of thing; `addr` and `port` are network order.
|
||||
|
|
|
|||
44
include/dev/kdev.h
Normal file
44
include/dev/kdev.h
Normal 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);
|
||||
|
|
@ -24,10 +24,14 @@
|
|||
#define AF_NETLINK 16
|
||||
#define PF_NETLINK AF_NETLINK
|
||||
#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_DGRAM 2
|
||||
#define SOCK_RAW 3
|
||||
#define SOCK_SEQPACKET 5
|
||||
|
||||
// Flags ORed into socket()'s and accept4()'s `type`/`flags` argument. Linux's
|
||||
// values, because a musl program passes Linux's constants.
|
||||
|
|
@ -337,6 +341,15 @@ struct socket {
|
|||
char data[SOCKBUF];
|
||||
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.
|
||||
struct socket *queue[NBACKLOG];
|
||||
int nqueue;
|
||||
|
|
|
|||
240
kernel/dev/kdev.c
Normal file
240
kernel/dev/kdev.c
Normal 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);
|
||||
}
|
||||
|
|
@ -39,6 +39,7 @@
|
|||
#include "uapi/errno.h"
|
||||
#include "fs/blkdev.h"
|
||||
#include "dev/pty.h"
|
||||
#include "core/uaccess.h"
|
||||
|
||||
// Inode numbering. Two directories and a flat table of nodes, so the number is
|
||||
// either a directory id or an index into devnodes[] offset past them.
|
||||
|
|
@ -57,6 +58,52 @@
|
|||
#define DEVFS_PTS0 8192ULL
|
||||
#define DEVFS_PTM0 12288ULL
|
||||
|
||||
// What udev adds to /dev: directories (/dev/input/by-path, /dev/disk/...) and
|
||||
// symlinks (/dev/fd, /dev/stdin, /dev/char/13:64, /dev/disk/by-id/...). The
|
||||
// device nodes themselves stay the kernel's; this is a small table of names
|
||||
// that live only in memory, as they would in a tmpfs, listed after the real
|
||||
// entries of whichever directory they are in. Nothing here can hold data: a
|
||||
// regular file in /dev is refused.
|
||||
#define DEVFS_EXTRA0 32768ULL
|
||||
#define NEXTRA 192
|
||||
#define EXTRA_NAME 48
|
||||
#define EXTRA_TARGET 112
|
||||
|
||||
struct extra {
|
||||
uchar used; // allocated (an inode exists or is about to)
|
||||
uchar linked; // has a name in a directory
|
||||
short type; // T_DIR or T_SYMLINK
|
||||
uint64 parent; // inum of the directory it is in
|
||||
ushort tlen;
|
||||
char name[EXTRA_NAME];
|
||||
char target[EXTRA_TARGET];
|
||||
};
|
||||
|
||||
static struct {
|
||||
struct spinlock lock;
|
||||
struct extra e[NEXTRA];
|
||||
} extras;
|
||||
|
||||
static int extras_ready;
|
||||
|
||||
static void
|
||||
extras_init(void)
|
||||
{
|
||||
if (!extras_ready) {
|
||||
initlock(&extras.lock, "devfsx");
|
||||
extras_ready = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
extra_of(uint64 inum)
|
||||
{
|
||||
if (inum < DEVFS_EXTRA0)
|
||||
return -1;
|
||||
uint64 i = inum - DEVFS_EXTRA0;
|
||||
return i < NEXTRA ? (int)i : -1;
|
||||
}
|
||||
|
||||
// Which directory a node lives in. Only two exist and a third would want a real
|
||||
// tree rather than a flag, which is the point at which this file should grow
|
||||
// one instead of adding DEV_DIR_THIRD.
|
||||
|
|
@ -158,7 +205,21 @@ devfs_iload(struct inode *ip)
|
|||
ip->major = ip->minor = 0;
|
||||
ip->size = 0;
|
||||
|
||||
if (ip->inum == DEVFS_ROOT || ip->inum == DEVFS_INPUT ||
|
||||
int x = extra_of(ip->inum);
|
||||
if (x >= 0) {
|
||||
extras_init();
|
||||
acquire(&extras.lock);
|
||||
if (extras.e[x].used) {
|
||||
ip->type = extras.e[x].type;
|
||||
ip->nlink = extras.e[x].type == T_DIR ? 2 : 1;
|
||||
ip->size = extras.e[x].tlen;
|
||||
ip->mode = extras.e[x].type == T_DIR ? 0755 : 0777;
|
||||
} else {
|
||||
ip->type = 0;
|
||||
ip->nlink = 0;
|
||||
}
|
||||
release(&extras.lock);
|
||||
} else if (ip->inum == DEVFS_ROOT || ip->inum == DEVFS_INPUT ||
|
||||
ip->inum == DEVFS_PTS) {
|
||||
ip->type = T_DIR;
|
||||
ip->nlink = 2;
|
||||
|
|
@ -193,16 +254,90 @@ devfs_iload(struct inode *ip)
|
|||
}
|
||||
|
||||
static void devfs_istore(struct inode *ip) { (void)ip; }
|
||||
static void devfs_ifree(struct inode *ip) { (void)ip; }
|
||||
static void
|
||||
devfs_ifree(struct inode *ip)
|
||||
{
|
||||
int x = extra_of(ip->inum);
|
||||
if (x < 0)
|
||||
return;
|
||||
extras_init();
|
||||
acquire(&extras.lock);
|
||||
extras.e[x].used = 0;
|
||||
release(&extras.lock);
|
||||
}
|
||||
static void devfs_truncate(struct inode *ip, uint64 n) { (void)ip; (void)n; }
|
||||
|
||||
// Never reached for a device node: fileread and filewrite see T_DEV and go
|
||||
// through devsw. Present because fs_ops has the slots, and answering -EIO is
|
||||
// better than a null pointer if something ever does arrive here.
|
||||
static int devfs_read(struct inode *ip, int u, uint64 d, uint64 o, uint n)
|
||||
{ (void)ip; (void)u; (void)d; (void)o; (void)n; return -1; }
|
||||
static int devfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n)
|
||||
{ (void)ip; (void)u; (void)s; (void)o; (void)n; return -1; }
|
||||
static int
|
||||
devfs_read(struct inode *ip, int u, uint64 d, uint64 o, uint n)
|
||||
{
|
||||
int x = extra_of(ip->inum);
|
||||
// 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
|
||||
streq(const char *a, const char *b)
|
||||
|
|
@ -211,12 +346,41 @@ streq(const char *a, const char *b)
|
|||
return *a == *b;
|
||||
}
|
||||
|
||||
static struct inode *
|
||||
extra_find(uint64 parent, const char *name)
|
||||
{
|
||||
extras_init();
|
||||
acquire(&extras.lock);
|
||||
for (int i = 0; i < NEXTRA; i++)
|
||||
if (extras.e[i].used && extras.e[i].linked && extras.e[i].parent == parent &&
|
||||
streq(name, extras.e[i].name)) {
|
||||
release(&extras.lock);
|
||||
return iget(DEVFS_DEV, DEVFS_EXTRA0 + (uint64)i);
|
||||
}
|
||||
release(&extras.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct inode *
|
||||
devfs_dirlookup(struct inode *dp, char *name, uint *poff)
|
||||
{
|
||||
if (poff)
|
||||
*poff = 0;
|
||||
|
||||
int dx = extra_of(dp->inum);
|
||||
if (dx >= 0) {
|
||||
extras_init();
|
||||
if (streq(name, "."))
|
||||
return iget(DEVFS_DEV, dp->inum);
|
||||
if (streq(name, "..")) {
|
||||
acquire(&extras.lock);
|
||||
uint64 up = extras.e[dx].parent;
|
||||
release(&extras.lock);
|
||||
return iget(DEVFS_DEV, up ? up : DEVFS_ROOT);
|
||||
}
|
||||
return extra_find(dp->inum, name);
|
||||
}
|
||||
|
||||
if (streq(name, ".") || (streq(name, "..") && dp->inum == DEVFS_ROOT))
|
||||
return iget(DEVFS_DEV, dp->inum);
|
||||
if (streq(name, ".."))
|
||||
|
|
@ -261,19 +425,85 @@ devfs_dirlookup(struct inode *dp, char *name, uint *poff)
|
|||
if (streq(name, blkdev_name((uint)b)))
|
||||
return iget(DEVFS_DEV, DEVFS_BLK0 + (uint64)b);
|
||||
|
||||
return extra_find(dp->inum, name);
|
||||
}
|
||||
|
||||
// What can be created here: a directory or a symlink, by ialloc and then
|
||||
// dirlink -- the order every filesystem is driven in. The device nodes
|
||||
// themselves are the kernel's: mknod answers EROFS by never getting an inode.
|
||||
static struct inode *
|
||||
devfs_ialloc(uint dev, short type)
|
||||
{
|
||||
(void)dev;
|
||||
if (type != T_DIR && type != T_SYMLINK)
|
||||
return 0;
|
||||
extras_init();
|
||||
acquire(&extras.lock);
|
||||
for (int i = 0; i < NEXTRA; i++)
|
||||
if (!extras.e[i].used) {
|
||||
memset(&extras.e[i], 0, sizeof(extras.e[i]));
|
||||
extras.e[i].used = 1;
|
||||
extras.e[i].type = type;
|
||||
release(&extras.lock);
|
||||
return iget(DEVFS_DEV, DEVFS_EXTRA0 + (uint64)i);
|
||||
}
|
||||
release(&extras.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// A read-only filesystem, and these say so rather than pretending. mknod on
|
||||
// /dev returning EROFS is the correct answer: the node either exists because
|
||||
// the kernel has the device, or it does not exist because it has not.
|
||||
static int devfs_dirlink(struct inode *dp, char *name, uint64 inum)
|
||||
{ (void)dp; (void)name; (void)inum; return -EROFS; }
|
||||
static int devfs_dirunlink(struct inode *dp, char *name, uint off)
|
||||
{ (void)dp; (void)name; (void)off; return -EROFS; }
|
||||
static int devfs_dirempty(struct inode *dp) { (void)dp; return 0; }
|
||||
static struct inode *devfs_ialloc(uint dev, short type)
|
||||
{ (void)dev; (void)type; return 0; }
|
||||
static int
|
||||
devfs_dirlink(struct inode *dp, char *name, uint64 inum)
|
||||
{
|
||||
int x = extra_of(inum);
|
||||
if (streq(name, ".") || streq(name, ".."))
|
||||
return 0; /* a new directory's own dots */
|
||||
if (x < 0 || strlen(name) >= EXTRA_NAME)
|
||||
return -EINVAL;
|
||||
extras_init();
|
||||
acquire(&extras.lock);
|
||||
if (!extras.e[x].used || extras.e[x].linked) {
|
||||
release(&extras.lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
extras.e[x].parent = dp->inum;
|
||||
extras.e[x].linked = 1;
|
||||
memmove(extras.e[x].name, name, strlen(name) + 1);
|
||||
release(&extras.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
devfs_dirempty(struct inode *dp)
|
||||
{
|
||||
if (extra_of(dp->inum) < 0)
|
||||
return 0;
|
||||
extras_init();
|
||||
acquire(&extras.lock);
|
||||
for (int i = 0; i < NEXTRA; i++)
|
||||
if (extras.e[i].used && extras.e[i].linked && extras.e[i].parent == dp->inum) {
|
||||
release(&extras.lock);
|
||||
return 0;
|
||||
}
|
||||
release(&extras.lock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
devfs_dirunlink(struct inode *dp, char *name, uint off)
|
||||
{
|
||||
(void)off;
|
||||
extras_init();
|
||||
acquire(&extras.lock);
|
||||
for (int i = 0; i < NEXTRA; i++)
|
||||
if (extras.e[i].used && extras.e[i].linked && extras.e[i].parent == dp->inum &&
|
||||
streq(name, extras.e[i].name)) {
|
||||
extras.e[i].linked = 0; /* the inode goes when nobody holds it */
|
||||
release(&extras.lock);
|
||||
return 0;
|
||||
}
|
||||
release(&extras.lock);
|
||||
return -EROFS; /* a device node is the kernel's */
|
||||
}
|
||||
|
||||
struct linux_dirent64 {
|
||||
uint64 d_ino;
|
||||
|
|
@ -312,8 +542,9 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
|
|||
int used = 0;
|
||||
uint64 n = *poff;
|
||||
|
||||
int isx = extra_of(dp->inum) >= 0;
|
||||
if (dp->inum != DEVFS_ROOT && dp->inum != DEVFS_INPUT &&
|
||||
dp->inum != DEVFS_PTS)
|
||||
dp->inum != DEVFS_PTS && !isx)
|
||||
return -1;
|
||||
|
||||
if (n == 0) {
|
||||
|
|
@ -326,6 +557,25 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
|
|||
n++;
|
||||
}
|
||||
|
||||
// A directory udev made: the dots, then what is in it.
|
||||
if (isx) {
|
||||
uint64 base = 2;
|
||||
extras_init();
|
||||
acquire(&extras.lock);
|
||||
for (int i = 0; i < NEXTRA; i++) {
|
||||
if (!extras.e[i].used || !extras.e[i].linked || extras.e[i].parent != dp->inum)
|
||||
continue;
|
||||
if (n > base) { base++; continue; }
|
||||
if (emit(out, cap, &used, DEVFS_EXTRA0 + (uint64)i,
|
||||
extras.e[i].type == T_DIR ? 4 : 10 /* DT_LNK */, extras.e[i].name) < 0)
|
||||
break;
|
||||
n++;
|
||||
base++;
|
||||
}
|
||||
release(&extras.lock);
|
||||
goto out;
|
||||
}
|
||||
|
||||
// /dev/pts: the dots, then the pairs that exist. Ordinal 2 + N is pair N,
|
||||
// whether or not the ones before it exist, so a pair opened between two
|
||||
// calls does not shift the rest.
|
||||
|
|
@ -378,6 +628,21 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
|
|||
base++;
|
||||
}
|
||||
|
||||
// What udev added to this directory, after everything the kernel put there.
|
||||
extras_init();
|
||||
acquire(&extras.lock);
|
||||
for (int i = 0; i < NEXTRA; i++) {
|
||||
if (!extras.e[i].used || !extras.e[i].linked || extras.e[i].parent != dp->inum)
|
||||
continue;
|
||||
if (n > base) { base++; continue; }
|
||||
if (emit(out, cap, &used, DEVFS_EXTRA0 + (uint64)i,
|
||||
extras.e[i].type == T_DIR ? 4 : 10, extras.e[i].name) < 0)
|
||||
break;
|
||||
n++;
|
||||
base++;
|
||||
}
|
||||
release(&extras.lock);
|
||||
|
||||
out:
|
||||
*poff = n;
|
||||
return used;
|
||||
|
|
|
|||
|
|
@ -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/<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 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/<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: {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -15,15 +15,18 @@
|
|||
// is the property sleep/wakeup actually requires. Thirty-two sockets carrying
|
||||
// GUI messages will not notice the contention.
|
||||
//
|
||||
// **Names are held in a table here, not in the filesystem.** A real Unix
|
||||
// socket appears in a directory listing as a special file, and binding one
|
||||
// creates that node. Doing that properly means a new inode type threaded
|
||||
// through both filesystems, iget, dirlookup and stat -- a VFS change, not a
|
||||
// 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.
|
||||
// What is lost is `ls -l /tmp/schcom` showing an `s`, and `unlink()` of a
|
||||
// socket path failing with ENOENT. Both are worth fixing when the VFS next
|
||||
// gets opened up; neither blocks anything.
|
||||
// **Names live in the filesystem.** bind() creates a socket node
|
||||
// (kernel/sys/sys_net.c) and connect() resolves a path to the inode and looks
|
||||
// for the socket bound to that inode, so every spelling of a path arrives at
|
||||
// the same place and the node can be stat()ed and unlink()ed.
|
||||
//
|
||||
// **Datagram and sequenced-packet sockets** share the ring with streams: each
|
||||
// message is a 16-byte header -- length and multicast groups, sender port or
|
||||
// pid, sender uid and gid -- and then its bytes, so a read takes exactly one
|
||||
// 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/defs.h"
|
||||
|
|
@ -113,6 +116,139 @@ sockinit(void)
|
|||
"the socket table, with buffered bytes and descriptors in flight");
|
||||
}
|
||||
|
||||
// SOCK_SEQPACKET and SOCK_DGRAM keep message boundaries. They ride in the same
|
||||
// ring as a stream, each message behind a four-byte length, so a write is one
|
||||
// message and a read takes exactly one -- the rest of a message that does not
|
||||
// fit the buffer is dropped, as Linux drops it. A message is never split, so
|
||||
// one has to fit the ring whole.
|
||||
static int
|
||||
is_packet(struct socket *s)
|
||||
{
|
||||
if(s->domain == AF_NETLINK)
|
||||
return s->nlproto == NETLINK_KOBJECT_UEVENT;
|
||||
return s->domain == AF_UNIX && (s->type == SOCK_DGRAM || s->type == SOCK_SEQPACKET);
|
||||
}
|
||||
|
||||
int
|
||||
sock_is_packet(struct socket *s)
|
||||
{
|
||||
return is_packet(s);
|
||||
}
|
||||
|
||||
// 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.
|
||||
static struct socket *
|
||||
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
|
||||
// than that more is coming. Blocking here would hang a getifaddrs() that
|
||||
// asked one question too many.
|
||||
if(s->domain == AF_NETLINK){
|
||||
if(s->domain == AF_NETLINK && !is_packet(s)){
|
||||
acquire(&stab.lock);
|
||||
uint have = s->nwrite - s->nread;
|
||||
if(have == 0){
|
||||
|
|
@ -430,8 +566,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;
|
||||
|
|
@ -439,6 +577,51 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
|
|||
sleep(&s->nread, &stab.lock);
|
||||
}
|
||||
|
||||
// One message, whole or truncated; descriptors ride with the header.
|
||||
if(is_packet(s) && s->nread != s->nwrite){
|
||||
uint len = 0, 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
|
||||
// exactly on one -- and takes them -- or is cut short so that it stops
|
||||
// before it, leaving the next read to start there.
|
||||
|
|
@ -519,6 +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
|
||||
// the read that follows never block, which matters because musl reads with
|
||||
// MSG_DONTWAIT and treats a short read as the end of the dump.
|
||||
if(s->domain == AF_NETLINK && is_packet(s)){
|
||||
// udevd announces what it did on group 2; the sender does not hear itself.
|
||||
uevent_dispatch(UEVENT_GROUP_UDEV, 0, addr, n, s);
|
||||
return n;
|
||||
}
|
||||
if(s->domain == AF_NETLINK){
|
||||
acquire(&stab.lock);
|
||||
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;
|
||||
}
|
||||
|
||||
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++){
|
||||
// Re-read every iteration: the peer can go away while we sleep below.
|
||||
dst = s->peer;
|
||||
|
|
@ -607,9 +838,11 @@ socknew(int domain, int type, int proto)
|
|||
|
||||
if(domain != AF_UNIX && domain != AF_INET && domain != AF_NETLINK)
|
||||
return 0;
|
||||
// AF_UNIX is stream-only here; AF_INET has datagrams too, and inet_new is
|
||||
// AF_UNIX: streams, and message-preserving SEQPACKET and DGRAM (connected
|
||||
// pairs and listeners; an unconnected DGRAM has no address to send to and
|
||||
// will be refused when it tries). AF_INET has datagrams too, and inet_new is
|
||||
// what decides whether it can serve the type asked for.
|
||||
if(domain == AF_UNIX && type != SOCK_STREAM)
|
||||
if(domain == AF_UNIX && type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET)
|
||||
return 0;
|
||||
// AF_NETLINK carries whole messages and musl asks for SOCK_RAW; SOCK_DGRAM is
|
||||
// accepted too because Linux does. NETLINK_ROUTE is the only protocol served
|
||||
|
|
@ -618,14 +851,16 @@ socknew(int domain, int type, int proto)
|
|||
if(domain == AF_NETLINK){
|
||||
if(type != SOCK_RAW && type != SOCK_DGRAM)
|
||||
return 0;
|
||||
if(proto != NETLINK_ROUTE)
|
||||
if(proto != NETLINK_ROUTE && proto != NETLINK_KOBJECT_UEVENT)
|
||||
return 0;
|
||||
}
|
||||
|
||||
acquire(&stab.lock);
|
||||
s = alloc_locked(type);
|
||||
if(s)
|
||||
if(s){
|
||||
s->domain = domain;
|
||||
s->nlproto = domain == AF_NETLINK ? proto : 0;
|
||||
}
|
||||
release(&stab.lock);
|
||||
if(s == 0)
|
||||
return 0;
|
||||
|
|
@ -644,16 +879,16 @@ socknew(int domain, int type, int proto)
|
|||
|
||||
// A connected pair with no name and no listener, for socketpair(2).
|
||||
int
|
||||
sockpair(struct socket **pa, struct socket **pb)
|
||||
sockpair_type(struct socket **pa, struct socket **pb, int type)
|
||||
{
|
||||
struct socket *a, *b;
|
||||
|
||||
acquire(&stab.lock);
|
||||
if((a = alloc_locked(SOCK_STREAM)) == 0){
|
||||
if((a = alloc_locked(type)) == 0){
|
||||
release(&stab.lock);
|
||||
return -ENFILE;
|
||||
}
|
||||
if((b = alloc_locked(SOCK_STREAM)) == 0){
|
||||
if((b = alloc_locked(type)) == 0){
|
||||
a->state = SS_FREE;
|
||||
a->ref = 0;
|
||||
release(&stab.lock);
|
||||
|
|
@ -674,6 +909,12 @@ sockpair(struct socket **pa, struct socket **pb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
sockpair(struct socket **pa, struct socket **pb)
|
||||
{
|
||||
return sockpair_type(pa, pb, SOCK_STREAM);
|
||||
}
|
||||
|
||||
// bind(2) for AF_UNIX. The caller has already created the filesystem node and
|
||||
// hands over its identity; see sys_bind() in kernel/sys/sys_net.c for why the
|
||||
// creation lives there and not here.
|
||||
|
|
@ -972,7 +1213,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.
|
||||
|
|
@ -1091,6 +1332,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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
@ -164,7 +165,7 @@ SYSCALL_DEFINE(socket)
|
|||
if (type & ~(0xFF | SOCK_CLOEXEC | SOCK_NONBLOCK))
|
||||
return -EINVAL;
|
||||
type &= 0xFF;
|
||||
if (domain == AF_UNIX && type != SOCK_STREAM)
|
||||
if (domain == AF_UNIX && type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET)
|
||||
return -EPROTOTYPE;
|
||||
if (domain == AF_INET && type != SOCK_STREAM && type != SOCK_DGRAM &&
|
||||
type != SOCK_RAW)
|
||||
|
|
@ -185,7 +186,7 @@ SYSCALL_DEFINE(socket)
|
|||
// default rather than departing from it, and programs do write them out:
|
||||
// toybox's `nc` passes IPPROTO_TCP, and refusing it meant "Protocol not
|
||||
// supported" for TCP on a system whose whole network is TCP.
|
||||
if (protocol != 0) {
|
||||
if (protocol != 0 && domain != AF_NETLINK) {
|
||||
int redundant =
|
||||
(domain == AF_INET && type == SOCK_STREAM && protocol == IPPROTO_TCP) ||
|
||||
(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));
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (sun_abstract(uaddr, ulen, &key))
|
||||
return sockbind_abstract(s, key);
|
||||
|
|
@ -459,15 +471,11 @@ SYSCALL_DEFINE(socketpair)
|
|||
return -EAFNOSUPPORT;
|
||||
int sflags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
|
||||
type &= 0xFF;
|
||||
// Datagram and sequenced-packet pairs are carried as a stream. What they
|
||||
// are used for between two processes is passing a descriptor or a status
|
||||
// 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 */)
|
||||
// Datagram and sequenced-packet pairs keep message boundaries (kernel/net/unix.c).
|
||||
if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET)
|
||||
return -EPROTOTYPE;
|
||||
|
||||
if ((r = sockpair(&a, &b)) < 0)
|
||||
if ((r = sockpair_type(&a, &b, type)) < 0)
|
||||
return r;
|
||||
|
||||
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
|
||||
// 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)
|
||||
|
|
@ -660,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)
|
||||
|
|
@ -965,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;
|
||||
|
|
@ -990,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;
|
||||
|
|
@ -1000,6 +1073,7 @@ SYSCALL_DEFINE(sendmsg)
|
|||
}
|
||||
if (len == 0)
|
||||
continue;
|
||||
anydata = 1;
|
||||
|
||||
if (addressed)
|
||||
w = socksendto(s, p, len, ip, port);
|
||||
|
|
@ -1030,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:
|
||||
|
|
@ -1040,6 +1120,7 @@ fail:
|
|||
|
||||
SYSCALL_DEFINE(recvmsg)
|
||||
{
|
||||
int didread = 0;
|
||||
struct socket *s;
|
||||
struct file *file;
|
||||
struct msghdr m;
|
||||
|
|
@ -1076,7 +1157,7 @@ SYSCALL_DEFINE(recvmsg)
|
|||
// one rather than discarding it as Linux would: musl's getifaddrs reads
|
||||
// in chunks, and this whole queue is one "datagram" only by convention.
|
||||
int nlret = -1;
|
||||
if (sock_domain(s) == AF_NETLINK) {
|
||||
if (sock_domain(s) == AF_NETLINK && !sock_is_packet(s)) {
|
||||
uint have = socknl_pending(s);
|
||||
if (have == 0)
|
||||
return -EAGAIN; // never blocks; see sockread
|
||||
|
|
@ -1113,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);
|
||||
|
|
@ -1127,8 +1209,21 @@ SYSCALL_DEFINE(recvmsg)
|
|||
return rd;
|
||||
}
|
||||
got += rd;
|
||||
if (rd < len)
|
||||
break;
|
||||
if (rd < len || sock_is_packet(s))
|
||||
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) {
|
||||
|
|
@ -1191,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) {
|
||||
|
|
@ -1234,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;
|
||||
|
|
|
|||
|
|
@ -56,3 +56,9 @@ curl
|
|||
# not ALSA's kernel side, and SDL 3 has no OSS backend of its own.
|
||||
alsa-lib
|
||||
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
|
||||
|
|
|
|||
|
|
@ -19,11 +19,12 @@ cairo-gobject-1.18.4-r1.apk ec302c75a24edaa4a2f867cfb5eec85cf30efe184044bab6e2b9
|
|||
c-ares-1.34.9-r0.apk 3539a3e11c1ef1516117d8179a546ac882086e94e0d78ad51d687b3fa2b3b5f2 main
|
||||
cmatrix-2.0-r2.apk d9f6bea3851492a6db2a9cd3f7e2df0f0756c6651a702b36ed2b887328ad1f00 community
|
||||
cups-libs-2.4.19-r1.apk 5739c568d90f23006d74ff82619e1a18447a6f698ae2af61970debe6658a7bea main
|
||||
curl-8.22.0-r0.apk 9262352e6341bb87e3047d1ca0b4051b2a9f4011409df25e4190e6be0334b860 main
|
||||
curl-zsh-completion-8.22.0-r0.apk 182a5f0afb5fd8786137f4610c065c305aa593fd18994df908c146335fb6011c main
|
||||
curl-8.23.0-r0.apk 531ae298451a4c1c44f563fc3b2eedc2c1d7e0a26a33bdfc0efe6faaff6a5942 main
|
||||
curl-zsh-completion-8.23.0-r0.apk 123ff531444fe6c3095c49553f8798bcf6f6ca8c7668b4140cc33daf24c351ec main
|
||||
dbus-libs-1.16.2-r2.apk 105f6ba17454c587ca9345f9deb348012d1950588a22da42120c954d396c99b6 main
|
||||
e2fsprogs-libs-1.47.4-r0.apk 2e067395a6981b06cfd2cd3abf0e6c6574f7ef64d02e01f75fc5323d4a337fb9 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
|
||||
fastfetch-2.64.2-r0.apk 9c593d689f1c04625d3b433066ad92e430a384fa8e99585fee71310a20a965cb 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
|
||||
hwdata-pci-0.408-r0.apk fe61404e6d2bd1e04c021d53641c0ebce97911084d77994e43a288a1278686c1 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-data-1.2.2-r0.apk 28ce98df27bfbb442fa397fbb9eb3eb5464e5b7b040ea034f207be52f4f4f07f community
|
||||
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
|
||||
libcom_err-1.47.4-r0.apk 4723d0fb8e286dc6adb54dd3cdaedcd8db54260f75ee778a5acb4c16922c0f52 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
|
||||
libdrm-2.4.134-r0.apk 584fd84ad5030040e613fa1816097cd1706ddd542100070852b0eab6e09e12ca 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
|
||||
tmux-3.7c-r0.apk 16f3438f5a24124dd500f70bf6f2a232c9b3dfe5e7f7e615969ac5c00c2c8d0b 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
|
||||
wayland-libs-client-1.25.0-r0.apk a728f7103217cafdc83ff6ad0d679c197dbbe280bd5788c8336259f7a39cb303 main
|
||||
wayland-libs-cursor-1.25.0-r0.apk 16ec369226507abaeb84b496ce2bb1a4ef29987ea91ba6bedc40bb0e0af274e7 main
|
||||
|
|
|
|||
|
|
@ -121,37 +121,26 @@ cat > "$P/root/.xinitrc" <<'XEOF'
|
|||
exec wmaker
|
||||
XEOF
|
||||
|
||||
# X's input devices, named rather than found. Alpine's Xorg asks udev for
|
||||
# them, SchOS has no udev, and X came up with no keyboard and no mouse: the
|
||||
# pointer did not move, Ctrl+Alt+F-keys did nothing and nothing could end the
|
||||
# session. SchOS has exactly two input nodes whatever the machine -- every
|
||||
# keyboard on event0, every mouse on event1 (kernel/dev/input.c) -- so a
|
||||
# fixed list is the whole answer. The file is SchOS's, in xorg.conf.d, which
|
||||
# no Alpine package writes into.
|
||||
# X's input devices are found, not named: udevd (etc/schinit/udevd.job) turns
|
||||
# the kernel's uevents into /run/udev data, and Xorg asks libudev for every
|
||||
# device with ID_INPUT set. That was a hand-written list of event0 and event1
|
||||
# while SchOS had no udev. The file is SchOS's, in xorg.conf.d, which no Alpine
|
||||
# package writes into; what is left here is the options the defaults do not
|
||||
# give, and the framebuffer driver.
|
||||
mkdir -p "$P/etc/X11/xorg.conf.d"
|
||||
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"
|
||||
Option "AutoAddDevices" "false"
|
||||
# Ctrl+Alt+Backspace ends X.
|
||||
Option "DontZap" "false"
|
||||
EndSection
|
||||
|
||||
Section "InputDevice"
|
||||
Identifier "keyboard"
|
||||
Driver "evdev"
|
||||
Option "Device" "/dev/input/event0"
|
||||
Section "InputClass"
|
||||
Identifier "SchOS keyboards"
|
||||
MatchIsKeyboard "on"
|
||||
Option "XkbRules" "evdev"
|
||||
Option "XkbOptions" "terminate:ctrl_alt_bksp"
|
||||
EndSection
|
||||
|
||||
Section "InputDevice"
|
||||
Identifier "mouse"
|
||||
Driver "evdev"
|
||||
Option "Device" "/dev/input/event1"
|
||||
EndSection
|
||||
|
||||
Section "Device"
|
||||
Identifier "fb"
|
||||
Driver "fbdev"
|
||||
|
|
@ -165,8 +154,6 @@ EndSection
|
|||
Section "ServerLayout"
|
||||
Identifier "schos"
|
||||
Screen "screen"
|
||||
InputDevice "keyboard" "CoreKeyboard"
|
||||
InputDevice "mouse" "CorePointer"
|
||||
EndSection
|
||||
XEOF
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue