mirror of
https://github.com/troglobit/finit.git
synced 2026-09-30 13:02:37 +07: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:
+6
-2
@@ -329,7 +329,11 @@ AS_IF([test "x$rtc_file" != "xno"], [
|
||||
AC_DEFINE_UNQUOTED(RTC_FILE, "$rtcfile_path", [Save and restore system time from this file if /dev/rtc is missing.])],[
|
||||
AC_DEFINE_UNQUOTED(RTC_FILE, NULL)])
|
||||
|
||||
AS_IF([test "x$with_keventd" != "xno"], [with_keventd=yes])
|
||||
AS_IF([test "x$with_keventd" != "xno"], [
|
||||
with_keventd=yes
|
||||
PKG_CHECK_MODULES([blkid], [blkid],, [
|
||||
AC_MSG_ERROR([libblkid (util-linux) is required to build keventd.
|
||||
Install the dev package (e.g. libblkid-dev) or disable keventd with --without-keventd])])])
|
||||
|
||||
AS_IF([test "x$with_sulogin" != "xno"], [
|
||||
AS_IF([test "x$sulogin" = "xyes"], [
|
||||
@@ -445,7 +449,7 @@ Optional features:
|
||||
Install doc/..........: $enable_doc
|
||||
Install contrib/......: $enable_contrib
|
||||
Bash completion.......: $bash_completion_dir
|
||||
Built-in keventd......: $with_keventd
|
||||
Built-in keventd......: $with_keventd (blkid: $blkid_LIBS)
|
||||
Built-in sulogin......: $with_sulogin $sulogin
|
||||
Built-in watchdogd....: $with_watchdog $watchdog
|
||||
Built-in logrotate....: $enable_logrotate
|
||||
|
||||
+1
-1
@@ -6,6 +6,6 @@ dist_docs_DATA = build.md cmdline.md commands.md conditions.md \
|
||||
distro.md example.md features.md index.md initctl.md \
|
||||
keventd.md plugins.md requirements.md runlevels.md \
|
||||
runparts.md service.md signals.md state-machine.md \
|
||||
watchdog.md
|
||||
udev-matching.md watchdog.md
|
||||
dist_docs_DATA += img/logo.png img/svc-machine.png img/svc-machine.svg \
|
||||
img/alpine-screenshot2.png
|
||||
|
||||
+17
-8
@@ -20,9 +20,11 @@ kernel events and handles:
|
||||
|
||||
- **Device node creation**: creates and removes `/dev` nodes with
|
||||
correct permissions on device add/remove events
|
||||
- **Persistent symlinks**: creates `/dev/disk/by-id/`, `/dev/disk/by-path/`,
|
||||
and `/dev/input/by-id/`, `/dev/input/by-path/` symlinks for stable
|
||||
device naming
|
||||
- **udev rules**: matches events against rules read from
|
||||
`/lib/udev/rules.d/`, `/run/udev/rules.d/`, and `/etc/udev/rules.d/`,
|
||||
with a curated ruleset derived from eudev installed by default
|
||||
- **Persistent symlinks**: creates `by-id` and `by-path` symlinks under
|
||||
`/dev/disk/`, `/dev/input/`, and elsewhere, for stable device naming
|
||||
- **Firmware loading**: responds to kernel firmware requests by searching
|
||||
`/lib/firmware/` and writing firmware data to sysfs
|
||||
- **Module loading**: parses `MODALIAS` from uevents and spawns `modprobe`
|
||||
@@ -70,19 +72,25 @@ device subsystem and name:
|
||||
Persistent Symlinks
|
||||
-------------------
|
||||
|
||||
For block devices, keventd creates symlinks under `/dev/disk/`:
|
||||
Symlinks come from two places. A handful are built into keventd and are
|
||||
created for every device in the subsystem, whatever rules are loaded.
|
||||
|
||||
For block devices, under `/dev/disk/`:
|
||||
|
||||
- **by-id**: based on the device serial number and model, read from
|
||||
sysfs attributes (`/sys/.../device/vendor`, `model`, `serial`)
|
||||
- **by-path**: based on the device topology path
|
||||
|
||||
For input devices, symlinks are created under `/dev/input/`:
|
||||
For input devices, under `/dev/input/`:
|
||||
|
||||
- **by-id**: based on the device name from sysfs
|
||||
- **by-path**: based on the physical device path
|
||||
|
||||
These symlinks are tracked internally and automatically removed when the
|
||||
corresponding device is unplugged.
|
||||
The rest come from `SYMLINK+=` in the udev rules, so what you get
|
||||
depends on the ruleset installed.
|
||||
|
||||
Built-in and rule-provided symlinks alike are tracked internally and
|
||||
removed when the corresponding device is unplugged.
|
||||
|
||||
|
||||
Firmware Loading
|
||||
@@ -221,7 +229,7 @@ battery:
|
||||
Usage
|
||||
-----
|
||||
|
||||
keventd [-cdGhnpv] [-g GROUP]
|
||||
keventd [-cdGhnpv] [-g GROUP] [-r DIR]
|
||||
|
||||
Options:
|
||||
-c Run coldplug at startup
|
||||
@@ -231,6 +239,7 @@ Usage
|
||||
-h Show help text
|
||||
-n Run in foreground (no daemon)
|
||||
-p Passive mode: power supply events only
|
||||
-r DIR Extra rules directory
|
||||
-v Show version
|
||||
|
||||
In normal operation, Finit starts keventd automatically via its system
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# How udev rules work
|
||||
|
||||
## Summary of the complete fix
|
||||
|
||||
- hardware.c: Write authorized_default=1 on the bus path before nftw
|
||||
runs (just 2 lines added). This exploits the fact that apath already
|
||||
contains path/authorized_default at that point (set by the snprintf on
|
||||
line 71). With authorized_default=1 set on the root hub, any device
|
||||
that appears behind an intermediate hub during hub enumeration gets
|
||||
auto-authorized by the kernel.
|
||||
- 70-usb-authorize.rules: Belt-and-suspenders for the async case. If hub
|
||||
port probing is slow enough that children appear after nftw has
|
||||
already returned, udev sees the add event with authorized=0, walks up
|
||||
the parent chain with ATTRS{authorized_default}=="1", and authorizes
|
||||
the device. Locked buses (authorized_default=0) are correctly skipped.
|
||||
|
||||
```
|
||||
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \
|
||||
ATTR{authorized}=="0", \
|
||||
ATTRS{authorized_default}=="1", \
|
||||
RUN+="/bin/sh -c 'echo 1 > /sys%p/authorized'"
|
||||
```
|
||||
|
||||
This keeps authorized_default=2 (deny by default) fully intact — the
|
||||
security window stays closed. The udev rule only fires when confd has
|
||||
explicitly marked a bus as unlocked.
|
||||
|
||||
## Comment from Undersigned
|
||||
|
||||
Very interesting, I did not know you could do such complex operations
|
||||
with udev rules. Could you explain a bit more in detail how the matching
|
||||
works and how we can guarantee locked buses/ports are not accidentally unlocked?
|
||||
|
||||
> Sure. Let's walk through it step by step.
|
||||
|
||||
## How udev ATTRS{} matching works
|
||||
|
||||
A udev rule matches against a device and its entire parent chain in the
|
||||
sysfs device tree. When a device like 2-1.4 (the keyboard) is added, its
|
||||
sysfs hierarchy looks like:
|
||||
|
||||
```
|
||||
/sys/devices/.../0000:01:00.0/usb2/ ← root hub (usbN)
|
||||
usb2/2-1/ ← VIA Labs hub
|
||||
usb2/2-1/2-1.4/ ← keyboard ← event fires here
|
||||
```
|
||||
|
||||
- ATTR{} (no trailing S) — matches attributes on the device itself only
|
||||
- ATTRS{} (with S) — matches attributes on the device or any of its
|
||||
parents, walking up the tree until it finds a match or runs out of
|
||||
parents
|
||||
|
||||
So ATTRS{authorized_default}=="1" on a keyboard event at 2-1.4 walks:
|
||||
|
||||
1. 2-1.4/authorized_default — doesn't exist (not a hub/controller)
|
||||
2. 2-1/authorized_default — doesn't exist (downstream hubs don't have
|
||||
this attribute, only root hubs do)
|
||||
3. usb2/authorized_default — exists, check its value
|
||||
|
||||
This is exactly the right level: authorized_default only lives on root
|
||||
hubs (usbN), which is precisely where confd writes.
|
||||
|
||||
## Why locked buses can't be accidentally unlocked
|
||||
|
||||
The invariant is: confd owns `authorized_default`. It sets it to 0
|
||||
(locked) or 1 (unlocked). The udev rule only reads it, never writes it.
|
||||
|
||||
Walk through the locked case:
|
||||
|
||||
```
|
||||
confd sets usb2/authorized_default = 0 ← locked
|
||||
```
|
||||
|
||||
A device plugs into usb2. udev fires:
|
||||
|
||||
1. ATTR{authorized}=="0" — matches (device is unauthorized)
|
||||
2. ATTRS{authorized_default}=="1" — walks up to usb2, finds 0 → no match
|
||||
3. Rule doesn't fire. Device stays denied. ✓
|
||||
|
||||
For a device behind an intermediate hub on a locked bus:
|
||||
|
||||
```
|
||||
usb2/authorized_default = 0 ← bus locked
|
||||
usb2/2-1/ ← hub (somehow authorized)
|
||||
usb2/2-1/2-1.4/ ← keyboard
|
||||
```
|
||||
|
||||
Even if the hub itself were authorized somehow, the keyboard's parent
|
||||
chain still reaches `usb2/authorized_default = 0` before finding any
|
||||
`authorized_default = 1`. The rule doesn't fire. ✓
|
||||
|
||||
The only remaining question: what if two buses exist and one is locked?
|
||||
|
||||
Say usb1 is unlocked and usb2 is locked. A device on usb2 fires a udev
|
||||
event. ATTRS walks up: `usb2/authorized_default = 0` — stops there (once
|
||||
it finds the attribute it stops). It does not continue up to PCIe or
|
||||
platform buses looking for usb1's attribute. Each USB device's parent
|
||||
chain only goes through its own root hub.
|
||||
|
||||
This is because authorized_default is found at usb2 — udev stops as soon
|
||||
as it matches the attribute name, regardless of value. If the value
|
||||
doesn't match the condition, the whole rule fails. It doesn't continue
|
||||
looking at other buses.
|
||||
|
||||
## Summary of the guarantee
|
||||
|
||||
```
|
||||
┌───────────────────────┬─────────────────────────┬─────────────────────────┐
|
||||
│ Scenario │ usb2/authorized_default │ Rule fires? │
|
||||
├───────────────────────┼─────────────────────────┼─────────────────────────┤
|
||||
│ Bus unlocked by confd │ 1 │ Yes → device authorized │
|
||||
├───────────────────────┼─────────────────────────┼─────────────────────────┤
|
||||
│ Bus locked by confd │ 0 │ No → device denied │
|
||||
├───────────────────────┼─────────────────────────┼─────────────────────────┤
|
||||
│ Bus at kernel default │ 2 │ No → device denied │
|
||||
├───────────────────────┼─────────────────────────┼─────────────────────────┤
|
||||
│ Bus doesn't exist yet │ — │ No → no match │
|
||||
└───────────────────────┴─────────────────────────┴─────────────────────────┘
|
||||
```
|
||||
|
||||
The rule is purely reactive: it can only authorize devices on buses that
|
||||
confd has already explicitly declared unlocked. It has no ability to
|
||||
override a lock.
|
||||
@@ -4,7 +4,17 @@ AM_CPPFLAGS += -I$(top_srcdir)/src
|
||||
pkglibexec_PROGRAMS = keventd
|
||||
|
||||
keventd_SOURCES = keventd.c keventd.h uevent.c \
|
||||
udevdb.c udevdb.h \
|
||||
sysfs.c sysfs.h \
|
||||
rules.c rules.h \
|
||||
builtin.c builtin.h \
|
||||
$(top_srcdir)/src/util.c $(top_srcdir)/src/util.h
|
||||
keventd_CFLAGS = -W -Wall -Wextra -std=gnu99
|
||||
# Silence noisy false positives from PATH_MAX-into-PATH_MAX sysfs path
|
||||
# concatenations; real concerns (e.g. USB strings into small buffers) are
|
||||
# handled via explicit checks where present. Matches systemd-udev policy.
|
||||
keventd_CFLAGS += -Wno-format-truncation
|
||||
keventd_CFLAGS += $(lite_CFLAGS)
|
||||
keventd_CFLAGS += $(blkid_CFLAGS)
|
||||
keventd_LDADD = $(lite_LIBS)
|
||||
keventd_LDADD += $(blkid_LIBS)
|
||||
|
||||
+1530
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
/* Builtin command dispatch for keventd rules engine
|
||||
*
|
||||
* Copyright (c) 2025 Joachim Wiberg <troglobit@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef KEVENTD_BUILTIN_H_
|
||||
#define KEVENTD_BUILTIN_H_
|
||||
|
||||
#include "keventd.h"
|
||||
|
||||
/*
|
||||
* Execute a builtin command from a udev rule.
|
||||
* cmd is the full command string, e.g. "hwdb --subsystem=input" or "path_id".
|
||||
* Returns 0 on success, -1 if the builtin is unknown or fails.
|
||||
*/
|
||||
int builtin_run(struct uevent *ev, const char *cmd);
|
||||
|
||||
/* True if NAME is a registered builtin (e.g. "path_id", "usb_id"). */
|
||||
int builtin_has(const char *name);
|
||||
|
||||
#endif /* KEVENTD_BUILTIN_H_ */
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
+79
-6
@@ -34,6 +34,7 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <getopt.h>
|
||||
@@ -60,6 +61,8 @@
|
||||
#endif
|
||||
|
||||
#include "keventd.h"
|
||||
#include "rules.h"
|
||||
#include "udevdb.h"
|
||||
#include "cond.h"
|
||||
#include "pid.h"
|
||||
#include "util.h"
|
||||
@@ -78,6 +81,10 @@ static int level;
|
||||
static int logon;
|
||||
static int passive; /* power-supply only, no device management */
|
||||
|
||||
static struct rule_list rules = TAILQ_HEAD_INITIALIZER(rules);
|
||||
static char *rules_dir; /* extra rules dir from -r option */
|
||||
static volatile sig_atomic_t reload_rules;
|
||||
|
||||
int debug; /* debug in other modules as well */
|
||||
|
||||
void logit(int prio, const char *fmt, ...)
|
||||
@@ -267,6 +274,27 @@ static void power_supply_change(const struct uevent *ev, char *buf, size_t len)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* True if rules have queued the kmod builtin for this event. Used to
|
||||
* suppress the direct modprobe path when 80-drivers.rules will fork
|
||||
* modprobe anyway -- otherwise coldplug runs modprobe twice per event.
|
||||
*/
|
||||
static int applied_has_kmod(const struct uevent *ev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ev->applied.nruncmds; i++) {
|
||||
const char *cmd = ev->applied.run_cmds[i];
|
||||
|
||||
if (ev->applied.run_types[i] != RUN_BUILTIN)
|
||||
continue;
|
||||
if (!strncmp(cmd, "kmod", 4) &&
|
||||
(cmd[4] == '\0' || isspace((unsigned char)cmd[4])))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a single uevent from the kernel.
|
||||
*/
|
||||
@@ -282,6 +310,10 @@ static void handle_uevent(char *buf, size_t len)
|
||||
ev.subsystem ?: "", ev.devname ?: "",
|
||||
ev.major, ev.minor);
|
||||
|
||||
/* Apply udev rules before built-in device handling */
|
||||
if (!passive)
|
||||
rules_apply(&rules, &ev);
|
||||
|
||||
switch (ev.action) {
|
||||
case ACT_ADD:
|
||||
if (!passive) {
|
||||
@@ -289,14 +321,17 @@ static void handle_uevent(char *buf, size_t len)
|
||||
if (ev.firmware)
|
||||
firmware_load(&ev);
|
||||
|
||||
/* Module loading */
|
||||
if (ev.modalias)
|
||||
if (ev.modalias && !applied_has_kmod(&ev))
|
||||
modprobe_load(ev.modalias);
|
||||
|
||||
/* Create device node if we have the info */
|
||||
if (ev.major >= 0 && ev.minor >= 0 && ev.devname)
|
||||
devnode_add(&ev);
|
||||
|
||||
/* Rename network interface if NAME= rule was applied */
|
||||
if (ev.subsystem && !strcmp(ev.subsystem, "net"))
|
||||
netdev_add(&ev);
|
||||
|
||||
/* Create symlinks */
|
||||
symlink_add(&ev);
|
||||
}
|
||||
@@ -307,8 +342,9 @@ static void handle_uevent(char *buf, size_t len)
|
||||
/* Remove symlinks first */
|
||||
symlink_del(&ev);
|
||||
|
||||
/* Remove device node */
|
||||
if (ev.devname)
|
||||
/* Remove device node, mirrors devnode_add(): net
|
||||
* interfaces carry a devname but have no node. */
|
||||
if (ev.major >= 0 && ev.minor >= 0 && ev.devname)
|
||||
devnode_del(&ev);
|
||||
}
|
||||
break;
|
||||
@@ -327,6 +363,20 @@ static void handle_uevent(char *buf, size_t len)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Execute RUN+= commands from matched rules */
|
||||
if (!passive)
|
||||
rules_run_cmds(&ev);
|
||||
|
||||
/* Persist device properties to /run/udev/data/ */
|
||||
if (!passive) {
|
||||
if (ev.action == ACT_REMOVE)
|
||||
udevdb_delete(&ev);
|
||||
else
|
||||
udevdb_write(&ev);
|
||||
}
|
||||
|
||||
uevent_env_free(&ev);
|
||||
}
|
||||
|
||||
static void init_power_supply(void)
|
||||
@@ -414,10 +464,16 @@ static void shut_down(int signo)
|
||||
running = 0;
|
||||
}
|
||||
|
||||
static void sighup_handler(int signo)
|
||||
{
|
||||
(void)signo;
|
||||
reload_rules = 1;
|
||||
}
|
||||
|
||||
static int usage(int rc)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage: keventd [-dGhnpv] [-c] [-g GROUP]\n"
|
||||
"Usage: keventd [-dGhnpv] [-c] [-g GROUP] [-r DIR]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -c Run coldplug at startup\n"
|
||||
@@ -427,6 +483,7 @@ static int usage(int rc)
|
||||
" -h Show this help text\n"
|
||||
" -n Run in foreground (no daemon)\n"
|
||||
" -p Passive mode: power supply events only (no device management)\n"
|
||||
" -r DIR Extra rules directory (in addition to standard udev paths)\n"
|
||||
" -v Show version\n"
|
||||
"\n", REBC_DEFAULT_NLGROUP);
|
||||
|
||||
@@ -460,7 +517,7 @@ int main(int argc, char *argv[])
|
||||
* requested bits verbatim instead of masking them. */
|
||||
umask(0);
|
||||
|
||||
while ((c = getopt(argc, argv, "cdg:Ghnpv")) != -1) {
|
||||
while ((c = getopt(argc, argv, "cdg:Ghnpr:v")) != -1) {
|
||||
switch (c) {
|
||||
case 'c':
|
||||
do_coldplug = 1;
|
||||
@@ -483,6 +540,9 @@ int main(int argc, char *argv[])
|
||||
case 'p':
|
||||
passive = 1;
|
||||
break;
|
||||
case 'r':
|
||||
rules_dir = optarg;
|
||||
break;
|
||||
case 'v':
|
||||
printf("keventd v%s\n", KEVENTD_VERSION);
|
||||
return 0;
|
||||
@@ -501,6 +561,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
signal(SIGUSR1, toggle_debug);
|
||||
signal(SIGTERM, shut_down);
|
||||
signal(SIGHUP, sighup_handler);
|
||||
signal(SIGCHLD, SIG_IGN); /* Don't wait for modprobe children */
|
||||
|
||||
/* Initialize condition directories */
|
||||
@@ -539,6 +600,10 @@ int main(int argc, char *argv[])
|
||||
if (do_coldplug)
|
||||
coldplug();
|
||||
|
||||
/* Load udev rules from standard directories (and -r extra_dir) */
|
||||
if (!passive)
|
||||
rules_load_all(&rules, rules_dir);
|
||||
|
||||
pidfile(NULL);
|
||||
logit(LOG_NOTICE, "keventd v%s started, waiting for events...", KEVENTD_VERSION);
|
||||
|
||||
@@ -546,6 +611,13 @@ int main(int argc, char *argv[])
|
||||
char rebc_buf[UEVENT_BUFFER_SIZE];
|
||||
int len;
|
||||
|
||||
/* Reload rules if SIGHUP was received */
|
||||
if (reload_rules) {
|
||||
reload_rules = 0;
|
||||
rules_free(&rules);
|
||||
rules_load_all(&rules, rules_dir);
|
||||
}
|
||||
|
||||
if (-1 == poll(&pfd, 1, -1)) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
@@ -592,6 +664,7 @@ int main(int argc, char *argv[])
|
||||
if (rebc_fd != -1)
|
||||
close(rebc_fd);
|
||||
close(pfd.fd);
|
||||
rules_free(&rules);
|
||||
logit(LOG_NOTICE, "keventd shutting down");
|
||||
|
||||
return 0;
|
||||
|
||||
+80
-9
@@ -29,11 +29,29 @@
|
||||
#else
|
||||
# include <lite/queue.h> /* BSD sys/queue.h API */
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/* Maximum uevent buffer size (kernel uses 8192 internally) */
|
||||
#define UEVENT_BUFFER_SIZE 8192
|
||||
|
||||
/* Capacity of the generic env store on struct uevent */
|
||||
#define UEVENT_ENV_MAX 64
|
||||
|
||||
/* Maximum TAG+= entries per device event */
|
||||
#define UEVENT_TAG_MAX 16
|
||||
|
||||
/* Maximum SYMLINK+= and RUN+= entries set by the rules engine */
|
||||
#define RULE_SYMLINK_MAX 8
|
||||
#define RULE_RUN_MAX 8
|
||||
|
||||
/* RUN{type} subtypes — also used in struct rule_ctx below */
|
||||
typedef enum {
|
||||
RUN_PROGRAM = 0, /* execute external program */
|
||||
RUN_BUILTIN, /* call builtin function */
|
||||
} run_type_t;
|
||||
|
||||
/* Uevent actions from kernel */
|
||||
typedef enum {
|
||||
ACT_UNKNOWN = 0,
|
||||
@@ -47,9 +65,36 @@ typedef enum {
|
||||
ACT_UNBIND,
|
||||
} uevent_action_t;
|
||||
|
||||
/*
|
||||
* Rule-applied permissions and context, set by the rules engine.
|
||||
* Zero-initialised means "nothing set by rules", fall back to built-in table.
|
||||
*/
|
||||
struct rule_ctx {
|
||||
/* Device permissions */
|
||||
mode_t mode;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
int has_mode;
|
||||
int has_owner;
|
||||
int has_group;
|
||||
int final_mode; /* := operator locks against further overrides */
|
||||
int final_owner;
|
||||
int final_group;
|
||||
/* NAME= override for device node path (heap-allocated) */
|
||||
char *name;
|
||||
/* SYMLINK+= accumulator (heap-allocated strings) */
|
||||
int nsymlinks;
|
||||
char *symlinks[RULE_SYMLINK_MAX];
|
||||
/* RUN+= command list (heap-allocated strings, executed post-event) */
|
||||
int nruncmds;
|
||||
char *run_cmds[RULE_RUN_MAX];
|
||||
run_type_t run_types[RULE_RUN_MAX];
|
||||
};
|
||||
|
||||
/*
|
||||
* Parsed uevent structure.
|
||||
* Pointers are into the receive buffer, zero-copy.
|
||||
* Named field pointers point into the receive buffer (zero-copy).
|
||||
* The env store holds all KEY=VALUE pairs plus any rule-set variables.
|
||||
*/
|
||||
struct uevent {
|
||||
uevent_action_t action;
|
||||
@@ -63,6 +108,23 @@ struct uevent {
|
||||
char *firmware; /* firmware file name request */
|
||||
char *seqnum; /* kernel sequence number */
|
||||
char *driver; /* driver name */
|
||||
|
||||
/* Generic env store: all KEY=VALUE from the netlink buffer + rule-set vars */
|
||||
int nenv;
|
||||
char *env_key[UEVENT_ENV_MAX];
|
||||
char *env_val[UEVENT_ENV_MAX];
|
||||
uint8_t env_key_alloc[UEVENT_ENV_MAX]; /* 1 if heap-allocated, 0 if buf ptr */
|
||||
uint8_t env_val_alloc[UEVENT_ENV_MAX];
|
||||
|
||||
/* PROGRAM= output, stored for subsequent RESULT== matching */
|
||||
char *result;
|
||||
|
||||
/* TAG+= accumulator */
|
||||
int ntags;
|
||||
char *tags[UEVENT_TAG_MAX];
|
||||
|
||||
/* Rule-applied permissions from the rules engine */
|
||||
struct rule_ctx applied;
|
||||
};
|
||||
|
||||
/* Tracked symlink for cleanup on device removal */
|
||||
@@ -73,19 +135,28 @@ struct dev_symlink {
|
||||
};
|
||||
|
||||
/* Function prototypes - uevent.c */
|
||||
int uevent_parse (char *buf, size_t len, struct uevent *ev);
|
||||
void rule_ctx_free (struct rule_ctx *ctx);
|
||||
|
||||
int uevent_parse (char *buf, size_t len, struct uevent *ev);
|
||||
const char *uevent_action_str(uevent_action_t action);
|
||||
const char *uevent_sysname (const struct uevent *ev);
|
||||
|
||||
int devnode_add (struct uevent *ev);
|
||||
int devnode_del (struct uevent *ev);
|
||||
const char *uevent_getenv (const struct uevent *ev, const char *key);
|
||||
int uevent_setenv (struct uevent *ev, const char *key, const char *val);
|
||||
void uevent_env_free (struct uevent *ev);
|
||||
|
||||
int symlink_add (struct uevent *ev);
|
||||
int symlink_del (struct uevent *ev);
|
||||
int devnode_add (struct uevent *ev);
|
||||
int devnode_del (struct uevent *ev);
|
||||
int netdev_add (struct uevent *ev);
|
||||
|
||||
int firmware_load (struct uevent *ev);
|
||||
int modprobe_load (const char *modalias);
|
||||
int symlink_add (struct uevent *ev);
|
||||
int symlink_del (struct uevent *ev);
|
||||
void symlink_write_db (const char *devpath, FILE *fp);
|
||||
|
||||
int coldplug (void);
|
||||
int firmware_load (struct uevent *ev);
|
||||
int modprobe_load (const char *modalias);
|
||||
|
||||
int coldplug (void);
|
||||
|
||||
#endif /* FINIT_KEVENTD_H_ */
|
||||
|
||||
|
||||
+1621
File diff suppressed because it is too large
Load Diff
+163
@@ -0,0 +1,163 @@
|
||||
/* udev rules file parser for keventd
|
||||
*
|
||||
* Copyright (c) 2025 Joachim Wiberg <troglobit@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef KEVENTD_RULES_H_
|
||||
#define KEVENTD_RULES_H_
|
||||
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/queue.h>
|
||||
#else
|
||||
# include <lite/queue.h>
|
||||
#endif
|
||||
|
||||
#include "keventd.h"
|
||||
|
||||
/* Operators from udev rules syntax */
|
||||
typedef enum {
|
||||
OP_INVALID = 0,
|
||||
OP_MATCH_EQ, /* == */
|
||||
OP_MATCH_NE, /* != */
|
||||
OP_ASSIGN, /* = */
|
||||
OP_ASSIGN_ADD, /* += */
|
||||
OP_ASSIGN_DEL, /* -= */
|
||||
OP_ASSIGN_FINAL, /* := */
|
||||
} rule_op_t;
|
||||
|
||||
/* All udev match and assignment key types */
|
||||
typedef enum {
|
||||
KEY_UNKNOWN = 0,
|
||||
/* Match keys — current device */
|
||||
KEY_ACTION,
|
||||
KEY_DEVPATH,
|
||||
KEY_KERNEL,
|
||||
KEY_NAME,
|
||||
KEY_SUBSYSTEM,
|
||||
KEY_DRIVER,
|
||||
KEY_ATTR, /* ATTR{filename} */
|
||||
KEY_SYSCTL, /* SYSCTL{parameter} */
|
||||
KEY_ENV, /* ENV{variable} */
|
||||
KEY_CONST, /* CONST{key} */
|
||||
KEY_TAG,
|
||||
KEY_TEST, /* TEST{mode} */
|
||||
KEY_PROGRAM,
|
||||
KEY_RESULT,
|
||||
/* Match keys — parent-chain walk */
|
||||
KEY_KERNELS,
|
||||
KEY_SUBSYSTEMS,
|
||||
KEY_DRIVERS,
|
||||
KEY_ATTRS, /* ATTRS{filename} */
|
||||
KEY_TAGS,
|
||||
/* Assignment keys */
|
||||
KEY_SYMLINK,
|
||||
KEY_OWNER,
|
||||
KEY_GROUP,
|
||||
KEY_MODE,
|
||||
KEY_SECLABEL, /* SECLABEL{module} */
|
||||
KEY_RUN, /* RUN{type} */
|
||||
KEY_LABEL,
|
||||
KEY_GOTO,
|
||||
KEY_IMPORT, /* IMPORT{type} */
|
||||
KEY_OPTIONS,
|
||||
} rule_key_class_t;
|
||||
|
||||
/* Pattern matching strategy for match-op keys */
|
||||
typedef enum {
|
||||
PAT_PLAIN = 0, /* literal strcmp */
|
||||
PAT_GLOB, /* fnmatch */
|
||||
PAT_SPLIT, /* pipe-separated alternatives */
|
||||
} pat_type_t;
|
||||
|
||||
/* IMPORT{type} subtypes */
|
||||
typedef enum {
|
||||
IMPORT_PROGRAM = 0, /* run program, parse KEY=VALUE output */
|
||||
IMPORT_FILE, /* read KEY=VALUE from file */
|
||||
IMPORT_DB, /* restore properties from udev database */
|
||||
IMPORT_BUILTIN, /* run builtin, parse output */
|
||||
IMPORT_PARENT, /* copy from parent device's database entry */
|
||||
IMPORT_CMDLINE, /* import matching key from /proc/cmdline */
|
||||
} import_type_t;
|
||||
|
||||
/* One key–operator–value triplet within a rule */
|
||||
struct rule_key {
|
||||
rule_key_class_t cls;
|
||||
rule_op_t op;
|
||||
char *attr; /* ATTR{x}/ENV{x}/… — heap-allocated, may be NULL */
|
||||
char *value; /* pattern or assignment value — heap-allocated */
|
||||
pat_type_t pat_type;
|
||||
import_type_t import_type;
|
||||
run_type_t run_type;
|
||||
};
|
||||
|
||||
#define RULE_KEY_MAX 32
|
||||
|
||||
/* One parsed rule (a single non-empty, non-comment line) */
|
||||
struct rule {
|
||||
TAILQ_ENTRY(rule) link;
|
||||
char *filename; /* heap-allocated source path */
|
||||
int lineno;
|
||||
int nkeys;
|
||||
struct rule_key keys[RULE_KEY_MAX];
|
||||
};
|
||||
|
||||
TAILQ_HEAD(rule_list, rule);
|
||||
|
||||
/*
|
||||
* Load rules from a single .rules file, appending to list.
|
||||
* Returns the number of rules loaded, or -1 on open failure.
|
||||
*/
|
||||
int rules_load (struct rule_list *list, const char *path);
|
||||
|
||||
/*
|
||||
* Load all *.rules files from the standard udev directories
|
||||
* (/lib/udev/rules.d, /run/udev/rules.d, /etc/udev/rules.d)
|
||||
* plus extra_dir if non-NULL. Files are loaded in alphanumeric
|
||||
* order within each directory; directories are processed in order.
|
||||
* Returns total number of rules loaded.
|
||||
*/
|
||||
int rules_load_all(struct rule_list *list, const char *extra_dir);
|
||||
|
||||
/*
|
||||
* Free all rules and release their resources.
|
||||
*/
|
||||
void rules_free (struct rule_list *list);
|
||||
|
||||
/*
|
||||
* Apply all matching rules from list to ev, updating ev->applied,
|
||||
* ev->result, env store, and tag list.
|
||||
*/
|
||||
int rules_apply (struct rule_list *list, struct uevent *ev);
|
||||
|
||||
/*
|
||||
* Execute the RUN+= commands accumulated in ev->applied.run_cmds.
|
||||
* Call after all device handling (nodes, symlinks) is complete.
|
||||
*/
|
||||
void rules_run_cmds(struct uevent *ev);
|
||||
|
||||
#endif /* KEVENTD_RULES_H_ */
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/* sysfs attribute reader and parent-chain walker
|
||||
*
|
||||
* Copyright (c) 2025 Joachim Wiberg <troglobit@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
#else
|
||||
# include <lite/lite.h>
|
||||
#endif
|
||||
|
||||
#include "sysfs.h"
|
||||
|
||||
int sysfs_read_file(const char *path, char *buf, size_t len)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(path, "r");
|
||||
if (!fp)
|
||||
return -1;
|
||||
|
||||
if (!fgets(buf, len, fp)) {
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
chomp(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sysfs_read_attr(const char *devpath, const char *attr,
|
||||
char *buf, size_t len)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
|
||||
snprintf(path, sizeof(path), "/sys%s/%s", devpath, attr);
|
||||
return sysfs_read_file(path, buf, len);
|
||||
}
|
||||
|
||||
int sysfs_parent_walk(const char *devpath,
|
||||
int (*cb)(const char *syspath, void *data),
|
||||
void *data)
|
||||
{
|
||||
char syspath[PATH_MAX];
|
||||
char *p;
|
||||
|
||||
snprintf(syspath, sizeof(syspath), "/sys%s", devpath);
|
||||
|
||||
while (1) {
|
||||
if (cb(syspath, data) == 0)
|
||||
return 0;
|
||||
|
||||
/* Strip last path component */
|
||||
p = strrchr(syspath, '/');
|
||||
if (!p || p == syspath)
|
||||
break;
|
||||
*p = 0;
|
||||
|
||||
/* Do not walk above /sys/devices */
|
||||
if (!strcmp(syspath, "/sys/devices") ||
|
||||
!strcmp(syspath, "/sys"))
|
||||
break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sysfs_read_subsystem(const char *syspath, char *buf, size_t len)
|
||||
{
|
||||
char lpath[PATH_MAX], target[PATH_MAX];
|
||||
const char *p;
|
||||
ssize_t n;
|
||||
|
||||
snprintf(lpath, sizeof(lpath), "%s/subsystem", syspath);
|
||||
n = readlink(lpath, target, sizeof(target) - 1);
|
||||
if (n < 0)
|
||||
return -1;
|
||||
target[n] = 0;
|
||||
|
||||
p = strrchr(target, '/');
|
||||
snprintf(buf, len, "%s", p ? p + 1 : target);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sysfs_read_driver(const char *syspath, char *buf, size_t len)
|
||||
{
|
||||
char lpath[PATH_MAX], target[PATH_MAX];
|
||||
const char *p;
|
||||
ssize_t n;
|
||||
|
||||
snprintf(lpath, sizeof(lpath), "%s/driver", syspath);
|
||||
n = readlink(lpath, target, sizeof(target) - 1);
|
||||
if (n < 0)
|
||||
return -1;
|
||||
target[n] = 0;
|
||||
|
||||
p = strrchr(target, '/');
|
||||
snprintf(buf, len, "%s", p ? p + 1 : target);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
@@ -0,0 +1,75 @@
|
||||
/* sysfs attribute reader and parent-chain walker for keventd
|
||||
*
|
||||
* Copyright (c) 2025 Joachim Wiberg <troglobit@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef KEVENTD_SYSFS_H_
|
||||
#define KEVENTD_SYSFS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/*
|
||||
* Read the first line from an absolute path.
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
int sysfs_read_file(const char *path, char *buf, size_t len);
|
||||
|
||||
/*
|
||||
* Read a sysfs attribute for the device at /sys{devpath}.
|
||||
* attr may be a relative path like "device/vendor" or "../serial".
|
||||
* Returns 0 on success, -1 if the attribute does not exist.
|
||||
*/
|
||||
int sysfs_read_attr(const char *devpath, const char *attr,
|
||||
char *buf, size_t len);
|
||||
|
||||
/*
|
||||
* Walk the sysfs device tree upward from devpath toward /sys/devices,
|
||||
* calling cb at each level. cb receives the absolute syspath and the
|
||||
* caller-supplied data pointer. Walk stops when cb returns 0 (found)
|
||||
* or the tree root is reached.
|
||||
* Returns 0 if cb returned 0, -1 if nothing matched.
|
||||
*/
|
||||
int sysfs_parent_walk(const char *devpath,
|
||||
int (*cb)(const char *syspath, void *data),
|
||||
void *data);
|
||||
|
||||
/*
|
||||
* Read the subsystem name at syspath by following the "subsystem"
|
||||
* symlink and returning its basename.
|
||||
* Returns 0 on success, -1 if there is no subsystem link.
|
||||
*/
|
||||
int sysfs_read_subsystem(const char *syspath, char *buf, size_t len);
|
||||
|
||||
/*
|
||||
* Read the driver name at syspath by following the "driver" symlink
|
||||
* and returning its basename.
|
||||
* Returns 0 on success, -1 if there is no driver link.
|
||||
*/
|
||||
int sysfs_read_driver(const char *syspath, char *buf, size_t len);
|
||||
|
||||
#endif /* KEVENTD_SYSFS_H_ */
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
@@ -0,0 +1,243 @@
|
||||
/* udev-compatible per-device database in /run/udev/data/
|
||||
*
|
||||
* Copyright (c) 2025 Joachim Wiberg <troglobit@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
#else
|
||||
# include <lite/lite.h>
|
||||
#endif
|
||||
|
||||
#include "keventd.h"
|
||||
#include "sysfs.h"
|
||||
#include "udevdb.h"
|
||||
|
||||
/* Forward declaration from keventd.c */
|
||||
void logit(int prio, const char *fmt, ...);
|
||||
|
||||
/*
|
||||
* Compute the database file path for a device.
|
||||
*
|
||||
* udev's keying scheme:
|
||||
* b<maj>:<min> block device
|
||||
* c<maj>:<min> character device
|
||||
* n<ifindex> network interface
|
||||
* +<subsystem>:<sysname> everything else
|
||||
*
|
||||
* This matches the layout that udevadm info, libudev, and eudev expect.
|
||||
*/
|
||||
static void udevdb_path(const struct uevent *ev, char *buf, size_t len)
|
||||
{
|
||||
const char *sysname = uevent_sysname(ev);
|
||||
const char *ifindex = NULL;
|
||||
|
||||
if (ev->subsystem && !strcmp(ev->subsystem, "net"))
|
||||
ifindex = uevent_getenv(ev, "IFINDEX");
|
||||
|
||||
if (ev->major >= 0 && ev->minor >= 0) {
|
||||
const char *t = (ev->subsystem && !strcmp(ev->subsystem, "block"))
|
||||
? "b" : "c";
|
||||
snprintf(buf, len, UDEVDB_PATH "/%s%d:%d", t, ev->major, ev->minor);
|
||||
} else if (ifindex) {
|
||||
snprintf(buf, len, UDEVDB_PATH "/n%s", ifindex);
|
||||
} else {
|
||||
snprintf(buf, len, UDEVDB_PATH "/+%s:%s",
|
||||
ev->subsystem ? ev->subsystem : "unknown",
|
||||
sysname ? sysname : "unknown");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Write device properties to the database.
|
||||
*
|
||||
* Called after devnode_add() and symlink_add() so that all S: records
|
||||
* can be captured via symlink_write_db().
|
||||
*/
|
||||
int udevdb_write(const struct uevent *ev)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
struct timeval tv;
|
||||
FILE *fp;
|
||||
int i;
|
||||
|
||||
if (!ev->devpath)
|
||||
return -1;
|
||||
|
||||
if (mkpath(UDEVDB_PATH, 0755) && errno != EEXIST)
|
||||
return -1;
|
||||
|
||||
udevdb_path(ev, path, sizeof(path));
|
||||
|
||||
fp = fopen(path, "w");
|
||||
if (!fp) {
|
||||
logit(LOG_DEBUG, "udevdb: cannot write %s: %s", path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Timestamp in microseconds since epoch */
|
||||
gettimeofday(&tv, NULL);
|
||||
fprintf(fp, "I:%llu\n",
|
||||
(unsigned long long)(tv.tv_sec * 1000000ULL + tv.tv_usec));
|
||||
|
||||
/* All environment variables / device properties */
|
||||
for (i = 0; i < ev->nenv; i++)
|
||||
fprintf(fp, "E:%s=%s\n", ev->env_key[i], ev->env_val[i]);
|
||||
|
||||
/* Symlinks created for this device */
|
||||
symlink_write_db(ev->devpath, fp);
|
||||
|
||||
/* Tags */
|
||||
for (i = 0; i < ev->ntags; i++) {
|
||||
fprintf(fp, "G:%s\n", ev->tags[i]);
|
||||
fprintf(fp, "Q:%s\n", ev->tags[i]);
|
||||
}
|
||||
|
||||
fprintf(fp, "V:1\n");
|
||||
fclose(fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read E: records from a database file into ev's env store.
|
||||
*/
|
||||
static int udevdb_load(const char *path, struct uevent *ev)
|
||||
{
|
||||
char line[1024];
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(path, "r");
|
||||
if (!fp)
|
||||
return -1;
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
char *val;
|
||||
|
||||
chomp(line);
|
||||
|
||||
if (line[0] != 'E' || line[1] != ':')
|
||||
continue;
|
||||
|
||||
val = strchr(line + 2, '=');
|
||||
if (!val)
|
||||
continue;
|
||||
*val++ = 0;
|
||||
|
||||
uevent_setenv(ev, line + 2, val);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read device properties back from the database into ev's env store.
|
||||
*
|
||||
* Used by IMPORT{db} in the rules engine (Phase 4) to recover properties
|
||||
* that were set during a previous event for the same device.
|
||||
*/
|
||||
int udevdb_read(struct uevent *ev)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
|
||||
if (!ev->devpath)
|
||||
return -1;
|
||||
|
||||
udevdb_path(ev, path, sizeof(path));
|
||||
|
||||
return udevdb_load(path, ev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the parent device's properties into ev's env store, for
|
||||
* IMPORT{parent} in the rules engine. The parent has no uevent to key
|
||||
* from, so its device ID is composed from sysfs (see udevdb_path()).
|
||||
*/
|
||||
int udevdb_read_parent(struct uevent *ev)
|
||||
{
|
||||
char ppath[PATH_MAX], syspath[PATH_MAX], dbfile[PATH_MAX];
|
||||
char subsys[64] = "unknown";
|
||||
char attr[64];
|
||||
const char *sn;
|
||||
char *slash;
|
||||
|
||||
if (!ev->devpath)
|
||||
return -1;
|
||||
|
||||
snprintf(ppath, sizeof(ppath), "%s", ev->devpath);
|
||||
slash = strrchr(ppath, '/');
|
||||
if (!slash || slash == ppath)
|
||||
return -1;
|
||||
*slash = 0;
|
||||
|
||||
sn = strrchr(ppath, '/');
|
||||
sn = sn ? sn + 1 : ppath;
|
||||
|
||||
snprintf(syspath, sizeof(syspath), "/sys%s", ppath);
|
||||
sysfs_read_subsystem(syspath, subsys, sizeof(subsys));
|
||||
|
||||
snprintf(dbfile, sizeof(dbfile), "%s/dev", syspath);
|
||||
if (!sysfs_read_file(dbfile, attr, sizeof(attr))) {
|
||||
snprintf(dbfile, sizeof(dbfile), UDEVDB_PATH "/%s%s",
|
||||
strcmp(subsys, "block") ? "c" : "b", attr);
|
||||
} else if (!strcmp(subsys, "net")) {
|
||||
snprintf(dbfile, sizeof(dbfile), "%s/ifindex", syspath);
|
||||
if (sysfs_read_file(dbfile, attr, sizeof(attr)))
|
||||
return -1;
|
||||
snprintf(dbfile, sizeof(dbfile), UDEVDB_PATH "/n%s", attr);
|
||||
} else {
|
||||
snprintf(dbfile, sizeof(dbfile), UDEVDB_PATH "/+%s:%s",
|
||||
subsys, sn);
|
||||
}
|
||||
|
||||
return udevdb_load(dbfile, ev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove a device's database entry on ACT_REMOVE.
|
||||
*/
|
||||
void udevdb_delete(const struct uevent *ev)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
|
||||
if (!ev->devpath)
|
||||
return;
|
||||
|
||||
udevdb_path(ev, path, sizeof(path));
|
||||
|
||||
if (unlink(path) && errno != ENOENT)
|
||||
logit(LOG_DEBUG, "udevdb: cannot remove %s: %s", path, strerror(errno));
|
||||
}
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
@@ -0,0 +1,72 @@
|
||||
/* udev-compatible per-device database in /run/udev/data/
|
||||
*
|
||||
* Copyright (c) 2025 Joachim Wiberg <troglobit@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef KEVENTD_UDEVDB_H_
|
||||
#define KEVENTD_UDEVDB_H_
|
||||
|
||||
#include "keventd.h"
|
||||
|
||||
/*
|
||||
* Database root directory, compatible with systemd/eudev's /run/udev/data/.
|
||||
* One text file per device, keyed by device ID (see udevdb_path()).
|
||||
*
|
||||
* Record format (one per line):
|
||||
* I:<usec> initialisation timestamp
|
||||
* E:<KEY>=<VAL> environment / property
|
||||
* S:<path> symlink (relative to /dev/)
|
||||
* G:<tag> tag (for G: records)
|
||||
* Q:<tag> tag (for Q: query records, mirrors G:)
|
||||
* V:1 format version sentinel, always last
|
||||
*/
|
||||
#define UDEVDB_PATH "/run/udev/data"
|
||||
|
||||
/*
|
||||
* Write a device's properties to the database after event processing.
|
||||
* Creates /run/udev/data/<id> with I:, E:, S:, G:, Q:, V: records.
|
||||
*/
|
||||
int udevdb_write (const struct uevent *ev);
|
||||
|
||||
/*
|
||||
* Read a device's properties from the database into ev's env store.
|
||||
* Used by IMPORT{db} and IMPORT{parent} in the rules engine (Phase 4).
|
||||
*/
|
||||
int udevdb_read (struct uevent *ev);
|
||||
|
||||
/*
|
||||
* Like udevdb_read(), but for the parent device of ev.
|
||||
*/
|
||||
int udevdb_read_parent (struct uevent *ev);
|
||||
|
||||
/*
|
||||
* Delete a device's database entry on ACT_REMOVE.
|
||||
*/
|
||||
void udevdb_delete (const struct uevent *ev);
|
||||
|
||||
#endif /* KEVENTD_UDEVDB_H_ */
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
+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;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ nav:
|
||||
- Plugins: plugins.md
|
||||
- Watchdog: watchdog.md
|
||||
- keventd: keventd.md
|
||||
- udev Rule Matching: udev-matching.md
|
||||
- D-Bus Integration: dbus.md
|
||||
- Service State Machine: state-machine.md
|
||||
- Distributions: distro.md
|
||||
|
||||
Reference in New Issue
Block a user