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
This commit is contained in:
Nexoniarz 2026-10-26 11:39:13 +01:00
commit 5d4d318ebf
10 changed files with 813 additions and 37 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

@ -541,7 +541,13 @@ 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 sockpair_type(struct socket **a, struct socket **b, int type);
int sock_is_packet(struct socket *s); int sock_is_packet(struct socket *s);
int sock_netlink_bind(struct socket *s, uint groups); uint sock_nl_groups(struct socket *s);
int sock_netlink_bind(struct socket *s, uint pid, uint groups);
int sock_uevent_send(struct socket *s, const char *msg, int len, uint dpid, uint dgroups);
uint sock_nl_from(struct socket *s);
uint sock_nl_pid(struct socket *s);
uint sock_nl_bound_groups(struct socket *s);
void dev_stat_numbers(int major, int minor, int *maj, int *min);
void uevent_send(uint groups, const char *msg, int len, struct socket *from); 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

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

@ -344,6 +344,11 @@ struct socket {
// AF_NETLINK only: which protocol, and the multicast groups bound. // AF_NETLINK only: which protocol, and the multicast groups bound.
int nlproto; int nlproto;
uint nlgroups; 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];

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

@ -274,6 +274,25 @@ static int
devfs_read(struct inode *ip, int u, uint64 d, uint64 o, uint n) devfs_read(struct inode *ip, int u, uint64 d, uint64 o, uint n)
{ {
int x = extra_of(ip->inum); 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) if (x < 0 || ip->type != T_SYMLINK)
return -1; return -1;
extras_init(); extras_init();
@ -300,6 +319,8 @@ static int
devfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n) devfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n)
{ {
int x = extra_of(ip->inum); 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) if (x < 0 || ip->type != T_SYMLINK || o != 0 || n >= EXTRA_TARGET)
return -1; return -1;
extras_init(); extras_init();

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

@ -132,43 +132,120 @@ sock_is_packet(struct socket *s)
return is_packet(s); return is_packet(s);
} }
// bind(2) on a netlink socket: only the multicast groups mean anything here. // Every packet in the ring is an eight-byte header and then the payload: the
// length with the multicast groups it arrived on in the top byte, and the port
// id of whoever sent it. Netlink receivers need both -- a datagram that says
// "no group" is unicast, and is believed only from the one sender they trust.
#define PKTHDR 16
// The multicast groups the datagram last read from this socket was sent to,
// and who sent it.
uint
sock_nl_groups(struct socket *s)
{
return s->domain == AF_NETLINK ? s->nllast : 0;
}
uint
sock_nl_from(struct socket *s)
{
return s->domain == AF_NETLINK ? s->nlfrom : 0;
}
uint
sock_nl_pid(struct socket *s)
{
return s->nlpid;
}
uint
sock_nl_bound_groups(struct socket *s)
{
return s->nlgroups;
}
static uint nlport = 4000;
// bind(2) on a netlink socket. A port id of 0 asks the kernel to choose one,
// which is how a socket becomes something a datagram can be addressed to.
int int
sock_netlink_bind(struct socket *s, uint groups) sock_netlink_bind(struct socket *s, uint pid, uint groups)
{ {
acquire(&stab.lock); acquire(&stab.lock);
s->nlgroups = groups; s->nlgroups = groups;
if(s->nlproto == NETLINK_KOBJECT_UEVENT && s->nlpid == 0)
s->nlpid = pid ? pid : ++nlport;
release(&stab.lock); release(&stab.lock);
return 0; return 0;
} }
// Queue one uevent datagram on every uevent socket listening to any of // Caller holds stab.lock. Append one packet to dst's ring; 0, or -1 if it
// `groups`, except `from`. A full queue drops the message, as Linux does: // does not fit.
// udev recovers with a trigger, and the sender must never wait on a reader. static int
void pkt_put(struct socket *dst, const char *msg, int len, uint groups, uint from,
uevent_send(uint groups, const char *msg, int len, struct socket *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; struct socket *s;
uint spid = from ? from->nlpid : 0;
if(len <= 0 || len + 4 > SOCKBUF) if(len <= 0)
return; return;
acquire(&stab.lock); acquire(&stab.lock);
for(s = stab.sock; s < stab.sock + NSOCK; s++){ for(s = stab.sock; s < stab.sock + NSOCK; s++){
if(s->state == SS_FREE || s == from || s->domain != AF_NETLINK || if(s->state == SS_FREE || s == from || s->domain != AF_NETLINK ||
s->nlproto != NETLINK_KOBJECT_UEVENT || !(s->nlgroups & groups)) s->nlproto != NETLINK_KOBJECT_UEVENT)
continue; continue;
if(s->nwrite - s->nread + (uint)len + 4 > SOCKBUF) uint g = groups & s->nlgroups;
if(dpid && s->nlpid == dpid)
g = 0; // unicast: no group, so the reader checks the sender
else if(!g)
continue; continue;
for(int i = 0; i < 4; i++) if(pkt_put(s, msg, len, g, spid, 0, 0) == 0)
s->data[s->nwrite++ % SOCKBUF] = (char)((uint)len >> (8 * i)); wakeup(&s->nread);
for(int i = 0; i < len; i++)
s->data[s->nwrite++ % SOCKBUF] = msg[i];
wakeup(&s->nread);
} }
release(&stab.lock); release(&stab.lock);
poll_kick(); 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)
@ -486,8 +563,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;
@ -497,9 +576,15 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
// One message, whole or truncated; descriptors ride with the header. // One message, whole or truncated; descriptors ride with the header.
if(is_packet(s) && s->nread != s->nwrite){ if(is_packet(s) && s->nread != s->nwrite){
uint len = 0; uint len = 0, from = 0, puid = 0, pgid = 0;
for(i = 0; i < 4; i++) for(i = 0; i < 4; i++)
len |= (uint)(uchar)s->data[(s->nread + i) % SOCKBUF] << (8 * 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){ if(s->nscm > 0 && s->scm[0].pos == s->nread){
got = s->scm[0].n; got = s->scm[0].n;
for(i = 0; i < got; i++) for(i = 0; i < got; i++)
@ -508,7 +593,13 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
s->scm[i - 1] = s->scm[i]; s->scm[i - 1] = s->scm[i];
s->nscm--; s->nscm--;
} }
s->nread += 4; s->nread += PKTHDR;
s->nllast = len >> 24;
s->nlfrom = from;
s->pk_uid = puid;
s->pk_gid = pgid;
s->pk_valid = 1;
len &= 0xFFFFFF;
uint take = len < (uint)n ? len : (uint)n; uint take = len < (uint)n ? len : (uint)n;
for(i = 0; i < (int)take; i++) for(i = 0; i < (int)take; i++)
addr[i] = s->data[(s->nread + i) % SOCKBUF]; addr[i] = s->data[(s->nread + i) % SOCKBUF];
@ -610,7 +701,7 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
// MSG_DONTWAIT and treats a short read as the end of the dump. // MSG_DONTWAIT and treats a short read as the end of the dump.
if(s->domain == AF_NETLINK && is_packet(s)){ if(s->domain == AF_NETLINK && is_packet(s)){
// udevd announces what it did on group 2; the sender does not hear itself. // udevd announces what it did on group 2; the sender does not hear itself.
uevent_send(UEVENT_GROUP_UDEV, addr, n, s); uevent_dispatch(UEVENT_GROUP_UDEV, 0, addr, n, s);
return n; return n;
} }
if(s->domain == AF_NETLINK){ if(s->domain == AF_NETLINK){
@ -643,7 +734,7 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
} }
if(is_packet(s)){ if(is_packet(s)){
if(n + 4 > SOCKBUF){ if(n + PKTHDR > SOCKBUF){
release(&stab.lock); release(&stab.lock);
return -EMSGSIZE; return -EMSGSIZE;
} }
@ -654,7 +745,7 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
sigsend(myproc()->pid, SIGPIPE); sigsend(myproc()->pid, SIGPIPE);
return -EPIPE; return -EPIPE;
} }
if(dst->nwrite - dst->nread + (uint)n + 4 <= SOCKBUF) if(dst->nwrite - dst->nread + (uint)n + PKTHDR <= SOCKBUF)
break; break;
if(myproc()->killed){ if(myproc()->killed){
release(&stab.lock); release(&stab.lock);
@ -674,10 +765,11 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
for(int j = 0; j < nf; j++) for(int j = 0; j < nf; j++)
b->f[j] = f[j]; b->f[j] = f[j];
} }
for(i = 0; i < 4; i++) // The sender's credentials travel with the datagram, not with the socket:
dst->data[dst->nwrite++ % SOCKBUF] = (char)((uint)n >> (8 * i)); // several processes can share one end (udev's workers do) and the reader
for(i = 0; i < n; i++) // has to know which of them each message came from.
dst->data[dst->nwrite++ % SOCKBUF] = addr[i]; pkt_put(dst, addr, n, 0, myproc() ? (uint)myproc()->pid : 0,
myproc() ? (uint)myproc()->euid : 0, myproc() ? (uint)myproc()->egid : 0);
wakeup(&dst->nread); wakeup(&dst->nread);
release(&stab.lock); release(&stab.lock);
poll_kick(); poll_kick();
@ -1118,7 +1210,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.
@ -1237,6 +1329,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"
@ -272,12 +273,13 @@ SYSCALL_DEFINE(bind)
// sockaddr_nl: family, pad, port id, groups. Only the groups matter here. // sockaddr_nl: family, pad, port id, groups. Only the groups matter here.
if (sock_domain(s) == AF_NETLINK) { if (sock_domain(s) == AF_NETLINK) {
uint groups = 0; uint pid = 0, groups = 0;
if (ulen < 12) if (ulen < 12)
return -EINVAL; return -EINVAL;
if (copy_from_user(&groups, uaddr + 8, 4) < 0) if (copy_from_user(&pid, uaddr + 4, 4) < 0 ||
copy_from_user(&groups, uaddr + 8, 4) < 0)
return -EFAULT; return -EFAULT;
return sock_netlink_bind(s, groups); return sock_netlink_bind(s, pid, groups);
} }
uint64 key; uint64 key;
@ -608,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)
@ -666,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)
@ -971,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;
@ -996,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;
@ -1006,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);
@ -1036,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:
@ -1046,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;
@ -1119,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);
@ -1137,6 +1213,19 @@ SYSCALL_DEFINE(recvmsg)
break; // one message per call 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) {
int room = 0, installed = 0, j; int room = 0, installed = 0, j;
uint64 writelen; uint64 writelen;
@ -1197,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) {
@ -1240,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;