mirror of
https://github.com/troglobit/finit.git
synced 2026-10-31 13:00:32 +00:00
keventd: add class/<subsystem>/<name> and driver/<name> conditions
dev/<X> only fires when a device gets a /dev node, which leaves a lot of
embedded-relevant hardware uncoverable: DSA switch cores, IIO sensors,
LEDs, backlight, PHYs, regulators -- all live purely under sysfs.
Two new condition namespaces:
class/<subsystem>/<sysname> asserted on every sysfs class device add
(e.g. <class/leds/blue>)
driver/<name> asserted while the driver is bound to at
least one device (e.g. <driver/mt7530>)
A driver can bind to several devices, so driver/ conditions are
refcounted: asserted on first bind, cleared when the last device is
unbound.
dev_cond() is generalized into a static cond_emit(prefix, rel, set) so
class_cond() and driver_cond() share the same mkpath + symlink/erase
code. The name avoids colliding with src/cond.h's public cond_path()
helper (unrelated function that returns a condition's filesystem path).
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
6 files changed
+174
-13
No files matched your search
+9
-1
@@ -177,7 +177,8 @@ Built-in Conditions
|
|||||||
|
|
||||||
Finit comes with a set of plugins for conditions:
|
Finit comes with a set of plugins for conditions:
|
||||||
|
|
||||||
- `keventd`: provides `<dev/...>` and `<sys/pwr/...>`
|
- `keventd`: provides `<dev/...>`, `<class/...>`, `<driver/...>`, and
|
||||||
|
`<sys/pwr/...>`
|
||||||
- `devmon` (built-in fallback for `<dev/...>` without keventd)
|
- `devmon` (built-in fallback for `<dev/...>` without keventd)
|
||||||
- `netlink`: provides `<net/...>`
|
- `netlink`: provides `<net/...>`
|
||||||
- `pidfile`: provides `<pid/...>`
|
- `pidfile`: provides `<pid/...>`
|
||||||
@@ -191,6 +192,11 @@ condition is cleared. If keventd is not in use (an external device
|
|||||||
manager like udevd is used instead), the `devmon` built-in provides the
|
manager like udevd is used instead), the `devmon` built-in provides the
|
||||||
same conditions by monitoring `/dev` and `/dev/dir` with inotify.
|
same conditions by monitoring `/dev` and `/dev/dir` with inotify.
|
||||||
|
|
||||||
|
keventd also asserts `class/<subsystem>/<name>` for devices that have
|
||||||
|
no `/dev` node, e.g. LEDs and DSA switch ports, and `driver/<name>`
|
||||||
|
while a driver is bound to at least one device. See
|
||||||
|
[keventd](keventd.md) for details.
|
||||||
|
|
||||||
The `pidfile` plugin recursively watches `/run/` for PID files created
|
The `pidfile` plugin recursively watches `/run/` for PID files created
|
||||||
by the monitored services, and sets a corresponding condition in the
|
by the monitored services, and sets a corresponding condition in the
|
||||||
`pid/` namespace.
|
`pid/` namespace.
|
||||||
@@ -234,6 +240,8 @@ Built-in conditions:
|
|||||||
- `usr/foo`
|
- `usr/foo`
|
||||||
- `boot/arg`
|
- `boot/arg`
|
||||||
- `dev/node` and `dev/dir/node`
|
- `dev/node` and `dev/dir/node`
|
||||||
|
- `class/<subsystem>/<name>`
|
||||||
|
- `driver/<name>`
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Here, `up` means administratively up, the interface flag `IFF_UP`.
|
> Here, `up` means administratively up, the interface flag `IFF_UP`.
|
||||||
|
|||||||
@@ -227,9 +227,60 @@ devices:
|
|||||||
command = "/usr/sbin/gps-daemon"
|
command = "/usr/sbin/gps-daemon"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Network interfaces are not device nodes and do not live in the `dev/`
|
||||||
|
namespace -- a `/dev/wan` node created by a user must not be confused
|
||||||
|
with a `wan` interface. To wait for an interface, use the `netlink`
|
||||||
|
plugin's `net/<iface>/exist` condition, plus `net/<iface>/up` (admin
|
||||||
|
up) and `net/<iface>/running` (carrier present) to gate on link state:
|
||||||
|
|
||||||
|
service dhcpcd {
|
||||||
|
description = "DHCP client"
|
||||||
|
runlevel = "2345"
|
||||||
|
conditions = { "net/wan/exist" }
|
||||||
|
command = "/usr/sbin/dhcpcd"
|
||||||
|
}
|
||||||
|
|
||||||
|
keventd provides the parallel `class/net/<iface>` condition, like for
|
||||||
|
any other sysfs class device.
|
||||||
|
|
||||||
When the device is removed, the condition is cleared and Finit stops
|
When the device is removed, the condition is cleared and Finit stops
|
||||||
the dependent services.
|
the dependent services.
|
||||||
|
|
||||||
|
### Class Conditions (`class/`)
|
||||||
|
|
||||||
|
Many devices live in sysfs without a `/dev/` node -- DSA switch ports,
|
||||||
|
IIO sensors, LEDs, backlight, PHYs, regulators. For those, keventd
|
||||||
|
asserts `class/<subsystem>/<sysname>` on every `add` event so services
|
||||||
|
can still wait for them:
|
||||||
|
|
||||||
|
service blink-blue {
|
||||||
|
description = "LED driver"
|
||||||
|
runlevel = "2345"
|
||||||
|
conditions = { "class/leds/blue" }
|
||||||
|
command = "/usr/sbin/blink-blue"
|
||||||
|
}
|
||||||
|
|
||||||
|
The condition is cleared on `remove`.
|
||||||
|
|
||||||
|
### Driver Conditions (`driver/`)
|
||||||
|
|
||||||
|
`driver/<name>` is asserted while the driver `<name>` is bound to at
|
||||||
|
least one device, from the kernel's `bind`/`unbind` uevents. Use this
|
||||||
|
to gate on slow-probing hardware whose readiness isn't marked by a
|
||||||
|
class device or `/dev` node, such as a switch core or complex PHY:
|
||||||
|
|
||||||
|
service dsa-probe {
|
||||||
|
description = "DSA topology probe"
|
||||||
|
runlevel = "2345"
|
||||||
|
conditions = { "driver/mt7530" }
|
||||||
|
command = "/usr/sbin/dsa-probe"
|
||||||
|
}
|
||||||
|
|
||||||
|
A driver bound to several devices keeps the condition asserted until
|
||||||
|
the last device is unbound. To wait for one specific device instance,
|
||||||
|
gate on what its probe creates instead: the `class/` condition or
|
||||||
|
`/dev` node of the child device.
|
||||||
|
|
||||||
### Power Supply Conditions (`sys/pwr/`)
|
### Power Supply Conditions (`sys/pwr/`)
|
||||||
|
|
||||||
keventd monitors the `power_supply` subsystem and provides:
|
keventd monitors the `power_supply` subsystem and provides:
|
||||||
|
|||||||
+12
-1
@@ -334,11 +334,17 @@ static void handle_uevent(char *buf, size_t len)
|
|||||||
|
|
||||||
/* Create symlinks */
|
/* Create symlinks */
|
||||||
symlink_add(&ev);
|
symlink_add(&ev);
|
||||||
|
|
||||||
|
/* class/<subsystem>/<sysname> -- covers sysfs-only
|
||||||
|
* devices that don't get a /dev node. */
|
||||||
|
class_cond(&ev, 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ACT_REMOVE:
|
case ACT_REMOVE:
|
||||||
if (!passive) {
|
if (!passive) {
|
||||||
|
class_cond(&ev, 0);
|
||||||
|
|
||||||
/* Remove symlinks first */
|
/* Remove symlinks first */
|
||||||
symlink_del(&ev);
|
symlink_del(&ev);
|
||||||
|
|
||||||
@@ -356,8 +362,13 @@ static void handle_uevent(char *buf, size_t len)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ACT_BIND:
|
case ACT_BIND:
|
||||||
|
if (!passive)
|
||||||
|
driver_cond(&ev, 1);
|
||||||
|
break;
|
||||||
|
|
||||||
case ACT_UNBIND:
|
case ACT_UNBIND:
|
||||||
/* Driver bind/unbind - could trigger conditions */
|
if (!passive)
|
||||||
|
driver_cond(&ev, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -149,6 +149,9 @@ int devnode_add (struct uevent *ev);
|
|||||||
int devnode_del (struct uevent *ev);
|
int devnode_del (struct uevent *ev);
|
||||||
int netdev_add (struct uevent *ev);
|
int netdev_add (struct uevent *ev);
|
||||||
|
|
||||||
|
void class_cond (const struct uevent *ev, int set);
|
||||||
|
void driver_cond (const struct uevent *ev, int set);
|
||||||
|
|
||||||
int symlink_add (struct uevent *ev);
|
int symlink_add (struct uevent *ev);
|
||||||
int symlink_del (struct uevent *ev);
|
int symlink_del (struct uevent *ev);
|
||||||
void symlink_write_db (const char *devpath, FILE *fp);
|
void symlink_write_db (const char *devpath, FILE *fp);
|
||||||
|
|||||||
+95
-11
@@ -58,37 +58,121 @@
|
|||||||
void logit(int prio, const char *fmt, ...);
|
void logit(int prio, const char *fmt, ...);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set/clear a device condition by creating/removing a symlink.
|
* Set/clear a Finit condition by creating/removing a symlink.
|
||||||
* keventd is a standalone daemon, so we manipulate the filesystem directly
|
* keventd is a standalone daemon, so we manipulate the filesystem directly
|
||||||
* rather than using Finit's internal cond_set()/cond_clear() API.
|
* rather than using Finit's internal cond_set()/cond_clear() API.
|
||||||
|
*
|
||||||
|
* prefix is the namespace path fragment (COND_DEV, COND_CLASS,
|
||||||
|
* COND_DRIVER, ...) ending in '/'. rel is the relative path under that
|
||||||
|
* namespace (e.g. "sda", "input/event0", "net/eth0", "mt7530").
|
||||||
*/
|
*/
|
||||||
static void dev_cond(const char *devname, int set)
|
static void cond_emit(const char *prefix, const char *rel, int set)
|
||||||
{
|
{
|
||||||
char cond[PATH_MAX];
|
char cond[PATH_MAX];
|
||||||
char *dir;
|
char *dir;
|
||||||
|
|
||||||
if (!devname)
|
if (!prefix || !rel || !*rel)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
snprintf(cond, sizeof(cond), "%s%s", _PATH_CONDDEV, devname);
|
snprintf(cond, sizeof(cond), "%s%s%s", _PATH_COND, prefix, rel);
|
||||||
|
|
||||||
/* Create parent directory if needed (e.g., dev/input/) */
|
/* mkpath is idempotent (EEXIST-tolerant) so just always call it. */
|
||||||
dir = strdupa(cond);
|
dir = strdupa(cond);
|
||||||
dir = dirname(dir);
|
dir = dirname(dir);
|
||||||
if (strcmp(dir, _PATH_CONDDEV)) {
|
if (mkpath(dir, 0755) && errno != EEXIST)
|
||||||
if (mkpath(dir, 0755) && errno != EEXIST)
|
logit(LOG_WARNING, "Failed creating condition dir %s", dir);
|
||||||
logit(LOG_WARNING, "Failed creating condition dir %s", dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (set) {
|
if (set) {
|
||||||
if (symlink(_PATH_RECONF, cond) && errno != EEXIST)
|
if (symlink(_PATH_RECONF, cond) && errno != EEXIST)
|
||||||
logit(LOG_WARNING, "Failed setting dev/%s condition", devname);
|
logit(LOG_WARNING, "Failed setting %s%s condition", prefix, rel);
|
||||||
} else {
|
} else {
|
||||||
if (erase(cond) && errno != ENOENT)
|
if (erase(cond) && errno != ENOENT)
|
||||||
logit(LOG_WARNING, "Failed clearing dev/%s condition", devname);
|
logit(LOG_WARNING, "Failed clearing %s%s condition", prefix, rel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void dev_cond(const char *devname, int set)
|
||||||
|
{
|
||||||
|
cond_emit(COND_DEV, devname, set);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* class/<subsystem>/<sysname> -- fires for any sysfs class device add,
|
||||||
|
* regardless of whether the device gets a /dev node. Lets services
|
||||||
|
* depend on sysfs-only hardware (DSA switch ports, LEDs, IIO sensors,
|
||||||
|
* PHYs, etc.).
|
||||||
|
*/
|
||||||
|
void class_cond(const struct uevent *ev, int set)
|
||||||
|
{
|
||||||
|
const char *sysname;
|
||||||
|
char rel[256];
|
||||||
|
|
||||||
|
if (!ev->subsystem || !ev->devpath)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sysname = strrchr(ev->devpath, '/');
|
||||||
|
sysname = sysname ? sysname + 1 : ev->devpath;
|
||||||
|
if (!*sysname)
|
||||||
|
return;
|
||||||
|
|
||||||
|
snprintf(rel, sizeof(rel), "%s/%s", ev->subsystem, sysname);
|
||||||
|
cond_emit(COND_CLASS, rel, set);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* driver/<name> -- asserted while the driver is bound to at least one
|
||||||
|
* device, from the kernel's bind/unbind uevents. Lets services depend
|
||||||
|
* on a specific driver having probed its hardware (e.g. <driver/mt7530>)
|
||||||
|
* even when no class device or /dev node marks the moment.
|
||||||
|
*
|
||||||
|
* A driver can bind to several devices, so the condition is refcounted:
|
||||||
|
* asserted on the first bind, cleared when the last device is unbound.
|
||||||
|
*/
|
||||||
|
struct drv_bind {
|
||||||
|
TAILQ_ENTRY(drv_bind) link;
|
||||||
|
char *name;
|
||||||
|
size_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
static TAILQ_HEAD(, drv_bind) drv_binds = TAILQ_HEAD_INITIALIZER(drv_binds);
|
||||||
|
|
||||||
|
void driver_cond(const struct uevent *ev, int set)
|
||||||
|
{
|
||||||
|
struct drv_bind *db;
|
||||||
|
|
||||||
|
if (!ev->driver)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TAILQ_FOREACH(db, &drv_binds, link) {
|
||||||
|
if (!strcmp(db->name, ev->driver))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (set) {
|
||||||
|
if (!db) {
|
||||||
|
db = calloc(1, sizeof(*db));
|
||||||
|
if (!db)
|
||||||
|
goto emit;
|
||||||
|
db->name = strdup(ev->driver);
|
||||||
|
if (!db->name) {
|
||||||
|
free(db);
|
||||||
|
goto emit;
|
||||||
|
}
|
||||||
|
TAILQ_INSERT_TAIL(&drv_binds, db, link);
|
||||||
|
}
|
||||||
|
if (db->count++)
|
||||||
|
return;
|
||||||
|
} else if (db) {
|
||||||
|
if (--db->count)
|
||||||
|
return;
|
||||||
|
TAILQ_REMOVE(&drv_binds, db, link);
|
||||||
|
free(db->name);
|
||||||
|
free(db);
|
||||||
|
}
|
||||||
|
emit:
|
||||||
|
cond_emit(COND_DRIVER, ev->driver, set);
|
||||||
|
}
|
||||||
|
|
||||||
/* Symlink tracking for cleanup on device removal */
|
/* Symlink tracking for cleanup on device removal */
|
||||||
static TAILQ_HEAD(, dev_symlink) symlinks = TAILQ_HEAD_INITIALIZER(symlinks);
|
static TAILQ_HEAD(, dev_symlink) symlinks = TAILQ_HEAD_INITIALIZER(symlinks);
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,16 @@
|
|||||||
|
|
||||||
#define COND_BASE "finit/cond"
|
#define COND_BASE "finit/cond"
|
||||||
#define COND_DEV "dev/"
|
#define COND_DEV "dev/"
|
||||||
|
#define COND_DRIVER "driver/"
|
||||||
|
#define COND_CLASS "class/"
|
||||||
#define COND_PID "pid/"
|
#define COND_PID "pid/"
|
||||||
#define COND_SYS "sys/"
|
#define COND_SYS "sys/"
|
||||||
#define COND_USR "usr/"
|
#define COND_USR "usr/"
|
||||||
|
|
||||||
#define _PATH_COND _PATH_VARRUN COND_BASE "/"
|
#define _PATH_COND _PATH_VARRUN COND_BASE "/"
|
||||||
#define _PATH_CONDDEV _PATH_COND COND_DEV
|
#define _PATH_CONDDEV _PATH_COND COND_DEV
|
||||||
|
#define _PATH_CONDDRIVER _PATH_COND COND_DRIVER
|
||||||
|
#define _PATH_CONDCLASS _PATH_COND COND_CLASS
|
||||||
#define _PATH_CONDPID _PATH_COND COND_PID
|
#define _PATH_CONDPID _PATH_COND COND_PID
|
||||||
#define _PATH_CONDSYS _PATH_COND COND_SYS
|
#define _PATH_CONDSYS _PATH_COND COND_SYS
|
||||||
#define _PATH_CONDUSR _PATH_COND COND_USR
|
#define _PATH_CONDUSR _PATH_COND COND_USR
|
||||||
|
|||||||
Reference in new issue
Block a user