mirror of
https://github.com/troglobit/finit.git
synced 2026-11-09 16:21:24 +00:00
keventd: add udev rules engine
Transform keventd from a power-supply monitor + basic hotplug handler into
a full udev-compatible device manager.
Rules engine (rules.c):
- Full .rules file parser covering all udev key types: ACTION, KERNEL,
SUBSYSTEM, DEVPATH, ENV, ATTR, SYSCTL, TAG, RESULT, PROGRAM, TEST,
parent-chain KERNELS/SUBSYSTEMS/ATTRS/DRIVERS, and more
- Pattern matching: plain string, fnmatch glob, and pipe-separated alternatives
- Operators: ==, !=, =, +=, -=, :=
- Assignments: NAME=, MODE=, OWNER=, GROUP=, SYMLINK+=, ENV{k}=, TAG+=, RUN+=
- IMPORT{program|file|builtin|parent|cmdline|db}=
- PROGRAM= with stdout capture for subsequent RESULT== matching
- GOTO=/LABEL= flow control
- Loads *.rules from /lib/udev/rules.d, /run/udev/rules.d, /etc/udev/rules.d
and an optional extra directory (-r DIR); reloads on SIGHUP
Builtin framework (builtin.c):
- kmod: load module by MODALIAS or explicit alias
- hwdb: match device against *.hwdb text files in udev hwdb dirs; builds
correct lookup key per subsystem — evdev:input:b*v*p*e* for input,
usb:v*p* for USB, raw modalias for PCI/platform
- path_id: build stable ID_PATH / ID_PATH_TAG from sysfs topology (PCI, USB,
ATA, NVMe, platform, ACPI, virtio)
- usb_id: read idVendor/idProduct/bcdDevice/serial from sysfs; look up
ID_VENDOR_FROM_DATABASE and ID_MODEL_FROM_DATABASE from usb.ids
(hwdata package) when available; silent fallback when absent
- input_id: classify input devices (keyboard, mouse, joystick, touchscreen,
touchpad) from evdev capability bitmasks in sysfs
- net_id: generate predictable names — MAC-based enx<mac> and PCI-slot-based
enp<bus>s<dev>[f<func>]
- blkid: probe filesystem type, UUID, and label via libblkid; sets ID_FS_*
and ID_PART_TABLE_* properties
Network interface renaming (uevent.c):
- netdev_add() renames interfaces via SIOCSIFNAME when a NAME= rule matched,
then sets the Finit dev/ condition on the final name; and any setup using
persistent interface naming via udev rules
Device node and symlink improvements (uevent.c):
- NAME=, MODE=, OWNER=, GROUP= overrides from matched rules applied at
mknod/chown time, falling back to the built-in permission table
- SYMLINK+= links from rules applied alongside built-in by-id/by-path links
Device property database (udevdb.c):
- Persist per-device E:/S:/I: records to /run/udev/data/ on ADD/CHANGE,
delete on REMOVE; IMPORT{db}= restores saved properties into event env
Build:
- libblkid (util-linux) is now required for keventd
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
17 files changed
+4451
-44
No files matched your search
+253
-18
@@ -34,12 +34,16 @@
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <net/if.h>
|
||||
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
#else
|
||||
@@ -229,6 +233,16 @@ int uevent_parse(char *buf, size_t len, struct uevent *ev)
|
||||
hdrlen = strlen(buf) + 1 + strlen(ev->devpath) + 1;
|
||||
i = hdrlen;
|
||||
|
||||
/* Seed env store with header fields not present as KEY=VALUE pairs */
|
||||
if (ev->nenv < UEVENT_ENV_MAX - 1) {
|
||||
ev->env_key[ev->nenv] = (char *)"ACTION";
|
||||
ev->env_val[ev->nenv] = buf; /* points to null-term'd action string */
|
||||
ev->nenv++;
|
||||
ev->env_key[ev->nenv] = (char *)"DEVPATH";
|
||||
ev->env_val[ev->nenv] = ev->devpath;
|
||||
ev->nenv++;
|
||||
}
|
||||
|
||||
/* Parse KEY=VALUE pairs */
|
||||
while (i < len) {
|
||||
char *eq;
|
||||
@@ -262,6 +276,14 @@ int uevent_parse(char *buf, size_t len, struct uevent *ev)
|
||||
ev->seqnum = eq;
|
||||
else if (!strcmp(line, "DRIVER"))
|
||||
ev->driver = eq;
|
||||
|
||||
/* Store every pair in the env store for rule matching */
|
||||
if (ev->nenv < UEVENT_ENV_MAX) {
|
||||
ev->env_key[ev->nenv] = line;
|
||||
ev->env_val[ev->nenv] = eq;
|
||||
/* alloc flags stay 0: buffer pointers, never freed */
|
||||
ev->nenv++;
|
||||
}
|
||||
}
|
||||
|
||||
i += strlen(line) + (eq ? strlen(eq) + 1 : 1) + 1;
|
||||
@@ -270,6 +292,108 @@ int uevent_parse(char *buf, size_t len, struct uevent *ev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *uevent_sysname(const struct uevent *ev)
|
||||
{
|
||||
const char *p;
|
||||
|
||||
if (!ev->devpath)
|
||||
return NULL;
|
||||
p = strrchr(ev->devpath, '/');
|
||||
return p ? p + 1 : ev->devpath;
|
||||
}
|
||||
|
||||
const char *uevent_getenv(const struct uevent *ev, const char *key)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ev->nenv; i++) {
|
||||
if (!strcmp(ev->env_key[i], key))
|
||||
return ev->env_val[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int uevent_setenv(struct uevent *ev, const char *key, const char *val)
|
||||
{
|
||||
char *v;
|
||||
int i;
|
||||
|
||||
/* Update value of an existing key */
|
||||
for (i = 0; i < ev->nenv; i++) {
|
||||
if (!strcmp(ev->env_key[i], key)) {
|
||||
if (!strcmp(ev->env_val[i], val))
|
||||
return 0;
|
||||
v = strdup(val);
|
||||
if (!v)
|
||||
return -1;
|
||||
if (ev->env_val_alloc[i])
|
||||
free(ev->env_val[i]);
|
||||
ev->env_val[i] = v;
|
||||
ev->env_val_alloc[i] = 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add new key — both key and value are heap-allocated */
|
||||
if (ev->nenv >= UEVENT_ENV_MAX)
|
||||
return -1;
|
||||
|
||||
i = ev->nenv;
|
||||
ev->env_key[i] = strdup(key);
|
||||
ev->env_val[i] = strdup(val);
|
||||
if (!ev->env_key[i] || !ev->env_val[i]) {
|
||||
free(ev->env_key[i]);
|
||||
free(ev->env_val[i]);
|
||||
return -1;
|
||||
}
|
||||
ev->env_key_alloc[i] = 1;
|
||||
ev->env_val_alloc[i] = 1;
|
||||
ev->nenv++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rule_ctx_free(struct rule_ctx *ctx)
|
||||
{
|
||||
int i;
|
||||
|
||||
free(ctx->name);
|
||||
ctx->name = NULL;
|
||||
|
||||
for (i = 0; i < ctx->nsymlinks; i++) {
|
||||
free(ctx->symlinks[i]);
|
||||
ctx->symlinks[i] = NULL;
|
||||
}
|
||||
ctx->nsymlinks = 0;
|
||||
|
||||
for (i = 0; i < ctx->nruncmds; i++) {
|
||||
free(ctx->run_cmds[i]);
|
||||
ctx->run_cmds[i] = NULL;
|
||||
}
|
||||
ctx->nruncmds = 0;
|
||||
}
|
||||
|
||||
void uevent_env_free(struct uevent *ev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ev->nenv; i++) {
|
||||
if (ev->env_key_alloc[i])
|
||||
free(ev->env_key[i]);
|
||||
if (ev->env_val_alloc[i])
|
||||
free(ev->env_val[i]);
|
||||
}
|
||||
ev->nenv = 0;
|
||||
|
||||
free(ev->result);
|
||||
ev->result = NULL;
|
||||
|
||||
for (i = 0; i < ev->ntags; i++)
|
||||
free(ev->tags[i]);
|
||||
ev->ntags = 0;
|
||||
|
||||
rule_ctx_free(&ev->applied);
|
||||
}
|
||||
|
||||
static struct devrule *find_rule(struct uevent *ev)
|
||||
{
|
||||
struct devrule *rule;
|
||||
@@ -308,6 +432,7 @@ int devnode_add(struct uevent *ev)
|
||||
{
|
||||
struct devrule *rule;
|
||||
char path[PATH_MAX];
|
||||
const char *devname;
|
||||
char *dir;
|
||||
mode_t mode;
|
||||
dev_t dev;
|
||||
@@ -316,7 +441,9 @@ int devnode_add(struct uevent *ev)
|
||||
if (!ev->devname || ev->major < 0 || ev->minor < 0)
|
||||
return -1;
|
||||
|
||||
snprintf(path, sizeof(path), "/dev/%s", ev->devname);
|
||||
/* NAME= in a rule overrides the kernel-supplied device name */
|
||||
devname = ev->applied.name ?: ev->devname;
|
||||
snprintf(path, sizeof(path), "/dev/%s", devname);
|
||||
|
||||
/* Create parent directories if needed (e.g., /dev/input/) */
|
||||
dir = strdupa(path);
|
||||
@@ -329,10 +456,10 @@ int devnode_add(struct uevent *ev)
|
||||
}
|
||||
}
|
||||
|
||||
/* Find matching rule for permissions */
|
||||
/* Built-in table provides defaults; rules engine may override */
|
||||
rule = find_rule(ev);
|
||||
mode = rule->mode;
|
||||
dev = makedev(ev->major, ev->minor);
|
||||
mode = ev->applied.has_mode ? ev->applied.mode : rule->mode;
|
||||
dev = makedev(ev->major, ev->minor);
|
||||
|
||||
/* Remove existing node if present */
|
||||
unlink(path);
|
||||
@@ -355,14 +482,19 @@ int devnode_add(struct uevent *ev)
|
||||
logit(LOG_WARNING, "Failed chmod %s: %s", path, strerror(errno));
|
||||
|
||||
/* Set ownership */
|
||||
if (chown(path, rule->uid, rule->gid))
|
||||
logit(LOG_WARNING, "Failed chown %s: %s", path, strerror(errno));
|
||||
{
|
||||
uid_t uid = ev->applied.has_owner ? ev->applied.uid : rule->uid;
|
||||
gid_t gid = ev->applied.has_group ? ev->applied.gid : rule->gid;
|
||||
|
||||
if (chown(path, uid, gid))
|
||||
logit(LOG_WARNING, "Failed chown %s: %s", path, strerror(errno));
|
||||
}
|
||||
|
||||
logit(LOG_DEBUG, "Created %s (%d:%d) mode %04o",
|
||||
path, ev->major, ev->minor, rule->mode);
|
||||
path, ev->major, ev->minor, mode & ~S_IFMT);
|
||||
|
||||
/* Set dev/ condition */
|
||||
dev_cond(ev->devname, 1);
|
||||
/* Set dev/ condition using the actual node name */
|
||||
dev_cond(devname, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -372,15 +504,18 @@ int devnode_add(struct uevent *ev)
|
||||
*/
|
||||
int devnode_del(struct uevent *ev)
|
||||
{
|
||||
const char *devname;
|
||||
char path[PATH_MAX];
|
||||
|
||||
if (!ev->devname)
|
||||
return -1;
|
||||
|
||||
snprintf(path, sizeof(path), "/dev/%s", ev->devname);
|
||||
/* Mirror devnode_add: NAME= overrides the kernel-supplied name */
|
||||
devname = ev->applied.name ?: ev->devname;
|
||||
snprintf(path, sizeof(path), "/dev/%s", devname);
|
||||
|
||||
/* Clear dev/ condition first */
|
||||
dev_cond(ev->devname, 0);
|
||||
dev_cond(devname, 0);
|
||||
|
||||
if (unlink(path) && errno != ENOENT) {
|
||||
logit(LOG_WARNING, "Failed removing %s: %s", path, strerror(errno));
|
||||
@@ -391,6 +526,52 @@ int devnode_del(struct uevent *ev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle network interface add: rename if NAME= was set by a rule.
|
||||
* Interfaces are not device nodes, so no dev/ condition is set here;
|
||||
* services depend on <net/IFNAME/exist> or <class/net/IFNAME> instead.
|
||||
*
|
||||
* The interface must be DOWN for SIOCSIFNAME to succeed. Freshly added
|
||||
* interfaces always are, so we do not attempt to bring the link down.
|
||||
*/
|
||||
int netdev_add(struct uevent *ev)
|
||||
{
|
||||
const char *new_name;
|
||||
|
||||
if (!ev->devname)
|
||||
return -1;
|
||||
|
||||
new_name = ev->applied.name;
|
||||
|
||||
if (new_name && strcmp(ev->devname, new_name)) {
|
||||
struct ifreq ifr;
|
||||
int sock, rc;
|
||||
|
||||
sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||
if (sock < 0) {
|
||||
logit(LOG_WARNING, "Cannot open socket for netif rename: %s",
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strncpy(ifr.ifr_name, ev->devname, IFNAMSIZ - 1);
|
||||
strncpy(ifr.ifr_newname, new_name, IFNAMSIZ - 1);
|
||||
|
||||
rc = ioctl(sock, SIOCSIFNAME, &ifr);
|
||||
close(sock);
|
||||
|
||||
if (rc < 0)
|
||||
logit(LOG_WARNING, "Failed renaming %s -> %s: %s",
|
||||
ev->devname, new_name, strerror(errno));
|
||||
else
|
||||
logit(LOG_NOTICE, "Renamed interface %s -> %s",
|
||||
ev->devname, new_name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Track symlink for removal when device is unplugged.
|
||||
*/
|
||||
@@ -625,18 +806,72 @@ static int symlink_add_input(struct uevent *ev)
|
||||
}
|
||||
|
||||
/*
|
||||
* Create appropriate symlinks based on device subsystem.
|
||||
* Write S: symlink records for a device into an already-open database file.
|
||||
* Called by udevdb_write() when building the per-device database entry.
|
||||
*/
|
||||
void symlink_write_db(const char *devpath, FILE *fp)
|
||||
{
|
||||
struct dev_symlink *sl;
|
||||
|
||||
TAILQ_FOREACH(sl, &symlinks, link) {
|
||||
if (strcmp(sl->devpath, devpath))
|
||||
continue;
|
||||
/* Store path relative to /dev/ */
|
||||
const char *rel = sl->linkpath;
|
||||
if (!strncmp(rel, "/dev/", 5))
|
||||
rel += 5;
|
||||
fprintf(fp, "S:%s\n", rel);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create appropriate symlinks based on device subsystem and rules.
|
||||
*/
|
||||
int symlink_add(struct uevent *ev)
|
||||
{
|
||||
if (!ev->subsystem)
|
||||
return 0;
|
||||
int i;
|
||||
|
||||
if (!strcmp(ev->subsystem, "block"))
|
||||
return symlink_add_disk(ev);
|
||||
/* Built-in subsystem symlinks */
|
||||
if (ev->subsystem) {
|
||||
if (!strcmp(ev->subsystem, "block"))
|
||||
symlink_add_disk(ev);
|
||||
else if (!strcmp(ev->subsystem, "input"))
|
||||
symlink_add_input(ev);
|
||||
}
|
||||
|
||||
if (!strcmp(ev->subsystem, "input"))
|
||||
return symlink_add_input(ev);
|
||||
/* SYMLINK+= from matched rules */
|
||||
for (i = 0; i < ev->applied.nsymlinks; i++) {
|
||||
const char *devname, *sl;
|
||||
char link[PATH_MAX], target[PATH_MAX];
|
||||
size_t toff = 0;
|
||||
int depth = 0;
|
||||
const char *p;
|
||||
|
||||
sl = ev->applied.symlinks[i];
|
||||
if (!sl || !*sl)
|
||||
continue;
|
||||
|
||||
devname = ev->applied.name ?: ev->devname;
|
||||
if (!devname)
|
||||
continue;
|
||||
|
||||
/* basename of devname for the symlink target */
|
||||
const char *base = strrchr(devname, '/');
|
||||
base = base ? base + 1 : devname;
|
||||
|
||||
/* Count '/' in symlink path to determine relative depth */
|
||||
for (p = sl; *p; p++)
|
||||
if (*p == '/')
|
||||
depth++;
|
||||
|
||||
target[0] = 0;
|
||||
for (int d = 0; d < depth; d++)
|
||||
toff += snprintf(target + toff, sizeof(target) - toff, "../");
|
||||
snprintf(target + toff, sizeof(target) - toff, "%s", base);
|
||||
|
||||
snprintf(link, sizeof(link), "/dev/%s", sl);
|
||||
symlink_create(target, link, ev->devpath ?: "");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user