mirror of
https://github.com/troglobit/finit.git
synced 2026-10-22 11:36:07 +00:00
Merge pull request #491 from finit-project/devman
Finit 5: udev/eudev replacement Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
94 files changed
+9744
-605
No files matched your search
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install pkg-config libconfuse-dev clang
|
||||
sudo apt-get -y install pkg-config libconfuse-dev clang libblkid-dev
|
||||
# clang picks the newest gcc tree it finds and needs the
|
||||
# matching libstdc++ headers to link the fuzzer runtime
|
||||
sudo apt-get -y install libstdc++-14-dev || true
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install pkg-config tree jq libcap-dev libconfuse-dev
|
||||
sudo apt-get -y install pkg-config tree jq libcap-dev libconfuse-dev libblkid-dev
|
||||
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
|
||||
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
|
||||
tar xf libuev-2.4.1.tar.xz
|
||||
@@ -149,3 +149,42 @@ jobs:
|
||||
with:
|
||||
name: finit-test-${{ matrix.compiler }}
|
||||
path: test/*.log
|
||||
|
||||
no-dbus:
|
||||
# The D-Bus support is default-enabled, so the HAVE_DBUS paths in
|
||||
# initctl and finit only bit-rot silently without this leg.
|
||||
name: no-dbus
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'push' || github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install pkg-config tree jq libcap-dev libconfuse-dev libblkid-dev
|
||||
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
|
||||
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
|
||||
tar xf libuev-2.4.1.tar.xz
|
||||
tar xf libite-2.6.2.tar.gz
|
||||
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
|
||||
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
|
||||
sudo ldconfig
|
||||
- uses: actions/checkout@v7
|
||||
- name: Build without D-Bus
|
||||
run: |
|
||||
./autogen.sh
|
||||
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc --localstatedir=/var \
|
||||
--disable-dbus --enable-testserv-plugin --with-keventd
|
||||
make -j9 V=1
|
||||
- name: Verify no bus artifacts
|
||||
run: |
|
||||
DESTDIR=/tmp make install-strip
|
||||
! strings /tmp/sbin/initctl | grep -q finit/bus
|
||||
! strings /tmp/sbin/finit | grep -q org.finit
|
||||
- name: Enable unprivileged userns (unshare)
|
||||
run: |
|
||||
sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
|
||||
- name: Smoke test
|
||||
run: |
|
||||
make -C test setup-chroot
|
||||
make -j1 -C test check TESTS='start-stop-serv.sh' \
|
||||
|| (cat test/test-suite.log; false)
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install pkg-config libcap-dev libconfuse-dev
|
||||
sudo apt-get -y install pkg-config libcap-dev libconfuse-dev libblkid-dev
|
||||
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
|
||||
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
|
||||
tar xf libuev-2.4.1.tar.xz
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Installing dependencies ...
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install pkg-config jq libcap-dev libconfuse-dev
|
||||
sudo apt-get -y install pkg-config jq libcap-dev libconfuse-dev libblkid-dev
|
||||
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
|
||||
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
|
||||
tar xf libuev-2.4.1.tar.xz
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install pkg-config jq libcap-dev libconfuse-dev
|
||||
sudo apt-get -y install pkg-config jq libcap-dev libconfuse-dev libblkid-dev
|
||||
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
|
||||
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
|
||||
tar xf libuev-2.4.1.tar.xz
|
||||
|
||||
@@ -11,6 +11,11 @@ if DBUS
|
||||
SUBDIRS += libink dbus-1
|
||||
endif
|
||||
SUBDIRS += src system tmpfiles.d
|
||||
|
||||
if KEVENTD
|
||||
SUBDIRS += keventd
|
||||
endif
|
||||
|
||||
dist_doc_DATA = README.md LICENSE contrib/finit.conf
|
||||
|
||||
if CONTRIB
|
||||
|
||||
+13
-4
@@ -18,6 +18,7 @@ AC_CONFIG_FILES([Makefile
|
||||
man/Makefile
|
||||
plugins/Makefile
|
||||
src/Makefile
|
||||
keventd/Makefile
|
||||
system/Makefile system/10-hotplug.conf
|
||||
test/test.env test/Makefile
|
||||
test/lib/Makefile test/src/Makefile
|
||||
@@ -124,7 +125,7 @@ AC_PLUGIN([resolvconf], [no], [Setup necessary files for resolvconf])
|
||||
AC_PLUGIN([x11-common], [no], [Console setup (for X)])
|
||||
AC_PLUGIN([netlink], [yes], [Basic netlink plugin for IFUP/IFDN and GW events. Can be replaced with externally built plugin that links with libnl or similar.])
|
||||
AC_PLUGIN([hook-scripts], [no], [Trigger script execution from hook points])
|
||||
AC_PLUGIN([hotplug], [yes], [Start udevd or mdev kernel event datamon])
|
||||
AC_PLUGIN([hotplug], [no], [Start udevd or mdev kernel event datamon])
|
||||
AC_PLUGIN([rtc], [yes], [Save and restore RTC using hwclock])
|
||||
AC_PLUGIN([tty], [yes], [Automatically activate new TTYs, e.g. USB-to-serial])
|
||||
AC_PLUGIN([urandom], [yes], [Setup and save random seed at boot/shutdown])
|
||||
@@ -177,7 +178,10 @@ AC_ARG_WITH(random-seed,
|
||||
[random_seed=$withval], [random_seed=yes])
|
||||
|
||||
AC_ARG_WITH(keventd,
|
||||
AS_HELP_STRING([--with-keventd], [Enable built-in keventd, default: no]),, [with_keventd=no])
|
||||
AS_HELP_STRING([--with-keventd], [Enable built-in keventd device manager, default: yes]),, [with_keventd=yes])
|
||||
|
||||
AC_ARG_WITH(udev-rules,
|
||||
AS_HELP_STRING([--without-udev-rules], [Skip install of curated udev rules under /lib/udev/rules.d, default: yes when keventd enabled]),, [with_udev_rules=yes])
|
||||
|
||||
AC_ARG_WITH(sulogin,
|
||||
AS_HELP_STRING([--with-sulogin@<:@=USER@:>@], [Enable built-in sulogin, optional USER to request password for (default root), default: no.]),[sulogin=$withval],[with_sulogin=no])
|
||||
@@ -328,7 +332,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"], [
|
||||
@@ -361,6 +369,7 @@ AS_IF([test "x$with_plugin_path" != "xno"], [
|
||||
AM_CONDITIONAL(BASH, [test "x$bash_dir" != "xno"])
|
||||
AM_CONDITIONAL(STATIC, [test "x$enable_static" = "xyes"])
|
||||
AM_CONDITIONAL(KEVENTD, [test "x$with_keventd" != "xno"])
|
||||
AM_CONDITIONAL(UDEV_RULES, [test "x$with_keventd" != "xno" -a "x$with_udev_rules" != "xno"])
|
||||
AM_CONDITIONAL(SULOGIN, [test "x$with_sulogin" != "xno"])
|
||||
AM_CONDITIONAL(WATCHDOGD, [test "x$with_watchdog" != "xno"])
|
||||
AM_CONDITIONAL(LIBSYSTEMD, [test "x$with_libsystemd" != "xno"])
|
||||
@@ -444,7 +453,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
|
||||
|
||||
+37
-31
@@ -8,6 +8,12 @@ All relevant changes are documented in this file.
|
||||
|
||||
### Changes
|
||||
|
||||
- The generated `/run/finit/system/*.conf` files for built-in services
|
||||
(keventd, watchdogd, runparts) are now read before
|
||||
`/lib/finit/system/*.conf` instead of after. Built-ins can now be
|
||||
referenced in `if:` statements from system .conf files and overridden
|
||||
by name from both `/lib/finit/system` and `/etc/finit.d`, the latter
|
||||
is still always read last
|
||||
- The `dbus.so` plugin, which starts an external `dbus-daemon`, is now
|
||||
enabled by default. It does nothing on systems without a
|
||||
`dbus-daemon` installed, and `--disable-dbus-plugin` opts out. The
|
||||
@@ -33,38 +39,38 @@ All relevant changes are documented in this file.
|
||||
private bus at `/run/finit/bus`, and -- opportunistically --
|
||||
registering `org.finit` on the standard system bus when a
|
||||
`dbus-daemon` is reachable. No external `libdbus`/`sd-bus`/`GIO`
|
||||
dependency.
|
||||
|
||||
The bus implements the stock `org.freedesktop.DBus`,
|
||||
`org.freedesktop.DBus.Peer`, `org.freedesktop.DBus.Introspectable`,
|
||||
and `org.freedesktop.DBus.Properties` interfaces, plus three
|
||||
Finit-specific ones:
|
||||
|
||||
* `org.finit.Manager1` at `/org/finit/manager` --
|
||||
`ListServices`, `GetService`, `Start`/`Stop`/`Restart`/`Reload`,
|
||||
`SetRunlevel`, `SetDebug`, `Signal`, `Suspend`, and the
|
||||
`Reboot`/`Halt`/`Poweroff` triplet. Read-only properties
|
||||
`Runlevel`, `PrevRunlevel`, `Version`. Signals
|
||||
`ServiceStateChanged (sss)` and `RunlevelChanged (ss)`.
|
||||
|
||||
* `org.finit.Service1` at `/org/finit/service/<encoded>` -- one
|
||||
object per loaded service, with `Start`/`Stop`/`Restart`/`Reload`
|
||||
for working off an object handle rather than passing the
|
||||
identity string around.
|
||||
|
||||
* `org.finit.Cond1` at `/org/finit/cond` -- `Get`, `Set`, `Clear`,
|
||||
`List`, `Dump` for [user-defined conditions](conditions.md),
|
||||
with a `ConditionChanged (ss)` signal.
|
||||
|
||||
Privileged methods accept root and members of the `--with-group`
|
||||
group; everyone else is refused. On the local bus the caller's uid
|
||||
and groups come straight from the kernel (`SO_PEERCRED` +
|
||||
`SO_PEERGROUPS`), so the check never blocks PID 1 on an NSS lookup.
|
||||
Over the system bus the uid is resolved from the broker
|
||||
(`GetConnectionUnixUser`) and privileged methods are root-only.
|
||||
Read-only methods are open.
|
||||
dependency. The bus implements the stock `org.freedesktop.DBus`,
|
||||
`Peer`, `Introspectable`, and `Properties` interfaces.
|
||||
See [D-Bus Integration](dbus.md) for the full surface, build flag,
|
||||
and `dbus-send`/`dbus-monitor` examples.
|
||||
and `dbus-send`/`dbus-monitor` examples
|
||||
- New `org.finit.Manager1` interface at `/org/finit/manager`:
|
||||
`ListServices`, `GetService`, `Start`/`Stop`/`Restart`/`Reload`,
|
||||
`SetRunlevel`, `SetDebug`, `Signal`, `Suspend`, and the
|
||||
`Reboot`/`Halt`/`Poweroff` triplet, which take a timeout argument
|
||||
arming the emergency shutdown bypass like `initctl -t`. Read-only
|
||||
properties `Runlevel`, `PrevRunlevel`, `Version`; signals
|
||||
`ServiceStateChanged (sss)` and `RunlevelChanged (ss)`
|
||||
- New `org.finit.Service1` interface at `/org/finit/service/<encoded>`,
|
||||
one object per loaded service, with `Start`/`Stop`/`Restart`/`Reload`
|
||||
for working off an object handle rather than passing the identity
|
||||
string around
|
||||
- New `org.finit.Cond1` interface at `/org/finit/cond`: `Get`, `Set`,
|
||||
`Clear`, `List`, `Dump` for [user-defined conditions](conditions.md),
|
||||
with a `ConditionChanged (ss)` signal
|
||||
- keventd serves `org.finit.Device1` on its own bus at
|
||||
`/run/keventd/bus`: `Settle`, `Trigger`, `Info`, `RulesReload`,
|
||||
queue-state properties, and a `DeviceProcessed (ss)` signal. The
|
||||
`udevadm` settle/trigger/info equivalents are thereby bus methods
|
||||
and `keventd -S` asks the running daemon first. Finit also gained a
|
||||
`Manager1.ConfigReloaded` signal, fired when `initctl reload`
|
||||
completes, for external condition providers and monitoring
|
||||
- Privileged D-Bus methods accept root and members of the
|
||||
`--with-group` group; everyone else is refused. On the local bus
|
||||
the caller's uid and groups come straight from the kernel
|
||||
(`SO_PEERCRED` + `SO_PEERGROUPS`), so the check never blocks PID 1
|
||||
on an NSS lookup. Over the system bus the uid is resolved from the
|
||||
broker (`GetConnectionUnixUser`) and privileged methods are
|
||||
root-only. Read-only methods are open
|
||||
|
||||
- `initctl` now transparently routes through D-Bus when the bus is
|
||||
reachable, with the legacy `INIT_SOCKET` transport as a fallback:
|
||||
|
||||
+2
-1
@@ -3,9 +3,10 @@ EXTRA_DIST = AUTHORS ChangeLog.md
|
||||
|
||||
docsdir := @docdir@/doc
|
||||
dist_docs_DATA = build.md cmdline.md commands.md conditions.md \
|
||||
dbus.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
|
||||
@@ -8,6 +8,13 @@ optional plugins to enable. It depends on three external libraries:
|
||||
- [libite][] (-lite), much needed frog DNA
|
||||
- [libConfuse][], the parser behind the .conf format
|
||||
|
||||
The bundled device manager, keventd, needs a fourth: libblkid from
|
||||
[util-linux][]. It reads the filesystem UUID and label off block
|
||||
devices, which is what the curated rules name the `/dev/disk/by-uuid/`
|
||||
and `/dev/disk/by-label/` symlinks after. keventd is enabled by
|
||||
default, so this is a hard requirement unless you build with
|
||||
`--without-keventd`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Most free/open source software packages that use `configure` default
|
||||
> to install to `/usr/local`. However, some Linux distributions do no
|
||||
@@ -24,6 +31,7 @@ version of the above mentioned libraries. Currently required versions:
|
||||
- libite v2.2.0, or later
|
||||
- libuEv v2.2.0, or later
|
||||
- libConfuse v3.3, or later
|
||||
- libblkid, any version, when keventd is enabled
|
||||
|
||||
|
||||
Configure
|
||||
@@ -67,6 +75,10 @@ Below are a few of the main switches to configure:
|
||||
|
||||
* `--enable-x11-common-plugin`: Enable the optional X Window `x11-common.so` plugin.
|
||||
|
||||
* `--without-keventd`: Drop the bundled device manager, for systems that
|
||||
run mdev, mdevd, or udevd instead. Enabled by default, and the only
|
||||
thing that pulls in libblkid. See [Bundled Device Manager](keventd.md).
|
||||
|
||||
* `--with-sulogin`: Enable bundled `sulogin` program. Default is to use the
|
||||
system `sulogin(8)`. The sulogin shipped with Finit *allows password-less*
|
||||
login if the `root` user is disabled or has no password at all.
|
||||
@@ -259,3 +271,4 @@ it only for debugging start up issues when Finit crashes.
|
||||
[libuEv]: https://github.com/troglobit/libuev
|
||||
[libite]: https://github.com/troglobit/libite
|
||||
[libConfuse]: https://github.com/libconfuse/libconfuse
|
||||
[util-linux]: https://github.com/util-linux/util-linux
|
||||
+18
-6
@@ -177,15 +177,25 @@ Built-in Conditions
|
||||
|
||||
Finit comes with a set of plugins for conditions:
|
||||
|
||||
- `devmon` (built-in)
|
||||
- `netlink`
|
||||
- `pidfile`
|
||||
- `keventd`: provides `<dev/...>`, `<class/...>`, `<driver/...>`, and
|
||||
`<sys/pwr/...>`
|
||||
- `devmon` (built-in fallback for `<dev/...>` without keventd)
|
||||
- `netlink`: provides `<net/...>`
|
||||
- `pidfile`: provides `<pid/...>`
|
||||
- `sys`
|
||||
- `usr`
|
||||
|
||||
The `devmon` (built-in) plugin monitors `/dev` and `/dev/dir` for device
|
||||
nodes being created and removed. It is active only when a run, task, or
|
||||
service has declared a `<dev/foo>` or `<dev/dir/bar>` condition.
|
||||
The `dev/` conditions are provided by `keventd`, the built-in device
|
||||
manager. When keventd creates a device node in `/dev`, it also asserts
|
||||
the corresponding `dev/` condition. When a device is removed, the
|
||||
condition is cleared. If keventd is not in use (an external device
|
||||
manager like udevd is used instead), the `devmon` built-in provides the
|
||||
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
|
||||
by the monitored services, and sets a corresponding condition in the
|
||||
@@ -230,6 +240,8 @@ Built-in conditions:
|
||||
- `usr/foo`
|
||||
- `boot/arg`
|
||||
- `dev/node` and `dev/dir/node`
|
||||
- `class/<subsystem>/<name>`
|
||||
- `driver/<name>`
|
||||
|
||||
> [!NOTE]
|
||||
> Here, `up` means administratively up, the interface flag `IFF_UP`.
|
||||
|
||||
+18
-14
@@ -24,8 +24,8 @@ the following layout is recommended:
|
||||
| `- ...
|
||||
`- run/
|
||||
`- finit/
|
||||
|- bus # Finit's own D-Bus socket, see doc/dbus.md
|
||||
`- system/
|
||||
|- dbus.conf
|
||||
|- keventd.conf
|
||||
|- runparts.conf
|
||||
|- watchdogd.conf
|
||||
@@ -35,18 +35,21 @@ Configuration files in `/etc` are provided by the user, or projects like
|
||||
[finit-skel](https://github.com/finit-project/finit-skel) and extended by
|
||||
the user.
|
||||
|
||||
The files in `/run/finit/system/*.conf` are generated by Finit for its
|
||||
built-in services: runparts, [watchdog](../watchdog.md), and
|
||||
[`keventd`](../keventd.md), if they are enabled. They are read before
|
||||
the other .conf directories, so any later file can reference them in
|
||||
`if:` statements and override them by name.
|
||||
|
||||
The files in `/lib/finit/system/*.conf` are system critical services and
|
||||
setup provided by Finit, e.g. udev/mdev that must run very early at system
|
||||
bootstrap. This system directory was introduced in Finit v4.4 to replace
|
||||
the hard-coded services provided by plugins before. All .conf files in this
|
||||
directory be either replaced by a system administrator or overridden by a
|
||||
file with the same name in `/etc/finit.d/`.
|
||||
the hard-coded services provided by plugins before.
|
||||
|
||||
The files in `/run/finit/system/*.conf` are created by plugins and Finit
|
||||
bundled services like runparts, [watchdog](../watchdog.md), and
|
||||
[`keventd`](../keventd.md) if they are enabled. Like
|
||||
`/lib/finit/system/*.conf`, these files can be overridden by file with
|
||||
the same name in `/etc/finit.d/`.
|
||||
All .conf files in both directories can be either replaced by a system
|
||||
administrator or overridden by a file with the same name in
|
||||
`/etc/finit.d/`, which is always read last -- copy a .conf there and
|
||||
modify it, or symlink it to `/dev/null` to disable it outright.
|
||||
|
||||
Services in the `available/` and `enabled/` sub-directories are called
|
||||
dynamic services, in contrast to static services -- the only difference
|
||||
@@ -55,16 +58,17 @@ with the `enable` and `disable` commands. An administrator can always
|
||||
create files and symlinks manually.
|
||||
|
||||
At bootstrap, and `initctl reload`, all .conf files are read, starting
|
||||
with `finit.conf`, then `/lib/finit/system/*.conf`, `finit.d/*.conf`,
|
||||
and finally all `finit.d/enabled/*.conf` files. Each directory is a
|
||||
unique group, where files within each group are sorted alphabetically.
|
||||
with `finit.conf`, then `/run/finit/system/*.conf`,
|
||||
`/lib/finit/system/*.conf`, `finit.d/*.conf`, and finally all
|
||||
`finit.d/enabled/*.conf` files. Each directory is a unique group,
|
||||
where files within each group are sorted alphabetically.
|
||||
|
||||
**Example:**
|
||||
|
||||
/run/finit/system/keventd.conf
|
||||
/run/finit/system/runparts.conf
|
||||
/lib/finit/system/10-hotplug.conf
|
||||
/lib/finit/system/90-testserv.conf
|
||||
/run/finit/system/dbus.conf
|
||||
/run/finit/system/runparts.conf
|
||||
/etc/finit.d/10-abc.conf
|
||||
/etc/finit.d/20-abc.conf
|
||||
/etc/finit.d/enabled/1-aaa.conf
|
||||
|
||||
@@ -125,8 +125,10 @@ Load kernel modules, each with optional arguments. Similar to the
|
||||
Deprecated, there is both a `modules-load.so` and a `modprobe.so` plugin
|
||||
that can handle module loading better. The former supports loading from
|
||||
`/etc/modules-load.d/`, the latter uses kernel modinfo to automatically
|
||||
load (or coldplug) every required module. For hotplug we recommend the
|
||||
BusyBox mdev tool, add to `/etc/mdev.conf`:
|
||||
load (or coldplug) every required module. Hotplug module loading is
|
||||
handled by [keventd](../keventd.md), the built-in device manager. On
|
||||
systems using the hotplug plugin with BusyBox mdev instead, add to
|
||||
`/etc/mdev.conf`:
|
||||
|
||||
$MODALIAS=.* root:root 0660 @modprobe -b "$MODALIAS"
|
||||
|
||||
|
||||
+44
-8
@@ -22,6 +22,10 @@ Bus address
|
||||
| Local (always) | `unix:path=/run/finit/bus` |
|
||||
| System (opportunistic) | `unix:path=/var/run/dbus/system_bus_socket` |
|
||||
|
||||
The bundled device manager serves its own `org.finit.Device1` the same
|
||||
way on `unix:path=/run/keventd/bus`, see [keventd](keventd.md); one
|
||||
socket per daemon, no forwarding between them.
|
||||
|
||||
The **local** bus is brokerless: clients connect straight to Finit over a
|
||||
Unix-domain socket using the standard D-Bus SASL EXTERNAL handshake. No
|
||||
`dbus-daemon` is required, which makes it suitable for embedded systems that
|
||||
@@ -33,6 +37,17 @@ The socket is `0660`, owned by `root` and the group given to
|
||||
does, so restricting one and not the other would leave the door open.
|
||||
Members of that group may use it, see [Authorization](#authorization).
|
||||
|
||||
Finit accepts at most 64 concurrent local-bus peers; further
|
||||
connections are refused until one disconnects. Match rules given to
|
||||
`AddMatch` support the `type`, `interface`, `member`, and `path` keys;
|
||||
`arg0` and its variants are not implemented.
|
||||
|
||||
For the system bus a standard busconfig policy ships in
|
||||
`dbus-1/org.finit.conf`: only root may own `org.finit`, unprivileged
|
||||
callers reach introspection, properties, and the read-only methods.
|
||||
Finit also enforces per-method authorization itself, so a permissive
|
||||
policy installed by mistake does not open state-changing methods.
|
||||
|
||||
The **system** bus is best-effort: Finit probes for a running `dbus-daemon`
|
||||
and, when reachable, claims the well-known name `org.finit` so that standard
|
||||
tooling sees Finit just like any other system service:
|
||||
@@ -67,7 +82,7 @@ Every node implements the usual stock interfaces:
|
||||
|
||||
| Interface | Purpose |
|
||||
|---------------------------------------|-------------------------------------------|
|
||||
| `org.freedesktop.DBus` | `Hello`, `AddMatch`, `RemoveMatch` |
|
||||
| `org.freedesktop.DBus` | `Hello`, `AddMatch`, `RemoveMatch` — on `/org/freedesktop/DBus` only |
|
||||
| `org.freedesktop.DBus.Peer` | `Ping`, `GetMachineId` |
|
||||
| `org.freedesktop.DBus.Introspectable` | `Introspect()` — XML description |
|
||||
| `org.freedesktop.DBus.Properties` | `Get`, `GetAll`; nothing is writable |
|
||||
@@ -91,11 +106,19 @@ and the service registry.
|
||||
| `Stop` | `s` | — | yes | Stop the service(s) matching the identity |
|
||||
| `Restart` | `s` | — | yes | Restart (stop + start) the service(s) |
|
||||
| `Reload` | — | — | yes | Re-read all `*.conf` and apply changes |
|
||||
| `SetRunlevel` | `u` | — | yes | Transition to runlevel `u` (0–6) |
|
||||
| `SetRunlevel` | `u` | — | yes | Transition to runlevel `u` (0–9) |
|
||||
| `SetDebug` | — | — | yes | Toggle Finit's runtime debug flag |
|
||||
| `Signal` | `su` | — | yes | Send signal `u` (1–31) to services matching identity `s` |
|
||||
| `Suspend` | — | — | yes | `sync()` + suspend-to-RAM |
|
||||
| `Reboot`, `Halt`, `Poweroff` | — | — | yes | Trigger the corresponding shutdown sequence |
|
||||
| `Reboot`, `Halt`, `Poweroff` | `u` | — | yes | Trigger the corresponding shutdown sequence; the argument arms the emergency fallback timer (seconds, 0 = none) |
|
||||
|
||||
Edge cases follow the legacy `initctl` protocol: `Suspend` and the
|
||||
reboot family reply `WrongRunlevel` during bootstrap and shutdown,
|
||||
`SetRunlevel` is a warning + no-op in runlevel 0/6 and records the
|
||||
level for the end of bootstrap in runlevel S, and `Signal` fails for
|
||||
stopped services. One deliberate divergence: a `SetRunlevel` argument
|
||||
outside 0–9 returns `InvalidArgs`, where the legacy protocol
|
||||
acknowledges silently.
|
||||
|
||||
### Properties
|
||||
|
||||
@@ -114,6 +137,7 @@ All read-only strings; observable via `Properties.Get` and
|
||||
|-----------------------|----------------------------------------|------------------------|
|
||||
| `ServiceStateChanged` | `sss` — identity, old state, new state | Service transitions |
|
||||
| `RunlevelChanged` | `ss` — old level, new level | System runlevel change |
|
||||
| `ConfigReloaded` | — | `initctl reload` completed; providers of generation-file conditions re-assert |
|
||||
|
||||
State names emitted by `ServiceStateChanged` are stable wire strings:
|
||||
`halted`, `done`, `dead`, `cleanup`, `teardown`, `stopping`, `setup`,
|
||||
@@ -135,7 +159,7 @@ constructing it by hand.
|
||||
| `Start` | — | — | yes | Equivalent to `Manager1.Start(<identity>)` for this service |
|
||||
| `Stop` | — | — | yes | … |
|
||||
| `Restart` | — | — | yes | … |
|
||||
| `Reload` | — | — | yes | Reload (SIGHUP if supported, else restart) |
|
||||
| `Reload` | — | — | yes | Send the service's `reload-signal`, default `SIGHUP`; restart when set to `"none"` |
|
||||
|
||||
### Properties
|
||||
|
||||
@@ -195,6 +219,18 @@ Lives at **`/org/finit/cond`**. Exposes Finit's
|
||||
Note: non-`usr/*` paths are rejected with `InvalidArgs` -- system
|
||||
conditions belong to Finit's state machine.
|
||||
|
||||
A service consuming such a condition:
|
||||
|
||||
service alarm {
|
||||
description = "Foo alarm"
|
||||
runlevel = "2345"
|
||||
conditions = { "usr/data-ready" }
|
||||
command = "alarm --arg foo"
|
||||
}
|
||||
|
||||
asserted from any bus client with `Cond1.Set string:"data-ready"`, see
|
||||
the examples below.
|
||||
|
||||
### Signals
|
||||
|
||||
| Signal | Body | Fires when |
|
||||
@@ -250,12 +286,12 @@ the following subcommands use the bus first:
|
||||
| `initctl restart` | `Manager1.Restart(svc)` |
|
||||
| `initctl reload` | `Manager1.Reload()` |
|
||||
| `initctl reload S` | `Service1.Reload()` (per-svc) |
|
||||
| `initctl reboot` | `Manager1.Reboot()` |
|
||||
| `initctl halt` | `Manager1.Halt()` |
|
||||
| `initctl poweroff` | `Manager1.Poweroff()` |
|
||||
| `initctl reboot` | `Manager1.Reboot(timeout)` |
|
||||
| `initctl halt` | `Manager1.Halt(timeout)` |
|
||||
| `initctl poweroff` | `Manager1.Poweroff(timeout)` |
|
||||
| `initctl suspend` | `Manager1.Suspend()` |
|
||||
| `initctl debug` | `Manager1.SetDebug()` |
|
||||
| `initctl signal` | `Manager1.Signal(svc, signo)` |
|
||||
| `initctl signal` (or `kill`) | `Manager1.Signal(svc, signo)` |
|
||||
| `initctl runlevel` | `Properties.Get(Manager1.Runlevel/PrevRunlevel)` |
|
||||
| `initctl cond set/get/clear` | `Cond1.{Set,Get,Clear}`, `clr` is an alias |
|
||||
|
||||
|
||||
@@ -275,6 +275,10 @@ initctl top # Interactive resource monitor
|
||||
initctl cond set usr/custom # Set custom condition
|
||||
```
|
||||
|
||||
`initctl` rides Finit's built-in [D-Bus API](dbus.md) when available,
|
||||
which is also open to any other bus client, including `dbus-send` and
|
||||
language bindings.
|
||||
|
||||
See the [Commands & Status](initctl.md) section for complete documentation.
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ Features
|
||||
* [Pre/Post script actions](config/service-wrappers.md)
|
||||
* Rudimentary [templating support](config/templating.md)
|
||||
* [Tooling](initctl.md) to enable/disable services
|
||||
* [D-Bus API](dbus.md), `org.finit` control and signals for bus clients
|
||||
* [Automatic reload](features.md#automatic-reload) of modified configuration files (optional)
|
||||
* [Built-in getty](config/tty.md)
|
||||
* Built-in watchdog, with support for hand-over to [watchdogd][]
|
||||
|
||||
+11
-10
@@ -5,6 +5,11 @@ Finit also implements a modern API to query status, and start/stop
|
||||
services, called `initctl`. Unlike `telinit` the `initctl` tool does
|
||||
not return until the given command has fully completed.
|
||||
|
||||
Since Finit v5, `initctl` talks to Finit over the built-in
|
||||
[D-Bus API](dbus.md) when the bus socket is available, falling back
|
||||
to the legacy `INIT_SOCKET` transport. The `monitor` command streams
|
||||
the `org.finit` signals until interrupted.
|
||||
|
||||
```
|
||||
Usage: initctl [OPTIONS] [COMMAND]
|
||||
|
||||
@@ -14,6 +19,7 @@ Options:
|
||||
-f, --force Ignore missing files and arguments, never prompt
|
||||
-h, --help This help text
|
||||
-j, --json JSON output in 'status' and 'cond' commands
|
||||
-n, --noerr Ignore error, e.g., already started/enabled/...
|
||||
-1, --once Only one lap in commands like 'top'
|
||||
-p, --plain Use plain table headings, no ctrl chars
|
||||
-q, --quiet Silent, only return status of command
|
||||
@@ -26,15 +32,8 @@ Commands:
|
||||
help This help text
|
||||
version Show program version
|
||||
|
||||
ls | list List all .conf in /etc/finit.d
|
||||
create <CONF> Create .conf in /etc/finit.d/available
|
||||
delete <CONF> Delete .conf in /etc/finit.d/available
|
||||
show <CONF> Show .conf in /etc/finit.d/available
|
||||
edit <CONF> Edit .conf in /etc/finit.d/available
|
||||
touch <CONF> Change .conf in /etc/finit.d/available
|
||||
enable <CONF> Enable .conf in /etc/finit.d/available
|
||||
disable <CONF> Disable .conf in /etc/finit.d/enabled
|
||||
reload Reload *.conf in /etc/finit.d (activate changes)
|
||||
show Show /etc/finit.conf
|
||||
reload Reload /etc/finit.conf (activate changes)
|
||||
|
||||
cond set <COND> Set (assert) user-defined conditions +usr/COND
|
||||
cond get <COND> Get status of user-defined condition, see $? and -v
|
||||
@@ -50,6 +49,7 @@ Commands:
|
||||
Note: Finit .conf file(s) are *not* reloaded!
|
||||
restart <NAME>[:ID] Restart (stop/start) service by name
|
||||
kill <NAME>[:ID] <S> Send signal S to service by name, with optional ID
|
||||
monitor Stream D-Bus signals (service state, conditions) until ^C
|
||||
ident [NAME] Show matching identities for NAME, or all
|
||||
status <NAME>[:ID] Show service status, by name
|
||||
status Show status of services, default command
|
||||
@@ -65,9 +65,10 @@ Commands:
|
||||
halt Halt system
|
||||
poweroff Halt and power off system
|
||||
suspend Suspend system
|
||||
switch-root NEWROOT [INIT] Switch to new root filesystem (initramfs only)
|
||||
switch-root ROOT [INIT] Switch to new root filesystem (initramfs)
|
||||
|
||||
utmp show Raw dump of UTMP/WTMP db
|
||||
|
||||
```
|
||||
|
||||
For services *not* supporting `SIGHUP` the `<!>` notation in the .conf
|
||||
|
||||
+424
-11
@@ -1,14 +1,427 @@
|
||||
keventd
|
||||
=======
|
||||
Bundled Device Manager
|
||||
======================
|
||||
|
||||
The kernel event daemon bundled with Finit is a simple uevent monitor
|
||||
for `/sys/class/power_supply`. It provides the `sys/pwr/ac` condition,
|
||||
which can be useful to prevent power hungry services like anacron to run
|
||||
when a laptop is only running on battery, for instance.
|
||||
The kernel event daemon `keventd` is a built-in device manager bundled
|
||||
with Finit. It replaces the need for external device managers like
|
||||
mdev, mdevd, or udevd on systems where a lighter-weight solution is
|
||||
preferred, particularly on embedded systems.
|
||||
|
||||
Since keventd is not an integral part of Finit yet it is not enabled by
|
||||
default. Enable it using `./configure --with-keventd`. The bundled
|
||||
`contrib/` build scripts for Debian, Alpine, and Void have this enabled.
|
||||
It is enabled by default since Finit v5, and needs libblkid from
|
||||
util-linux (the `libblkid-dev` package, or whatever your distribution
|
||||
calls it) to read the filesystem UUID and label behind the
|
||||
`/dev/disk/by-uuid/` and `/dev/disk/by-label/` symlinks. To disable it
|
||||
and use an external device manager instead:
|
||||
|
||||
This daemon is planned to be extended with monitoring of other uevents,
|
||||
patches and ideas are welcome in the issue tracker.
|
||||
./configure --without-keventd
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
When started, keventd listens on a `NETLINK_KOBJECT_UEVENT` socket for
|
||||
kernel events and handles:
|
||||
|
||||
- **Device node creation**: creates and removes `/dev` nodes with
|
||||
correct permissions on device add/remove events
|
||||
- **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`
|
||||
to load the appropriate kernel module
|
||||
- **Coldplug**: with the `-c` flag, walks `/sys/devices` and triggers
|
||||
add events for all devices present at boot
|
||||
- **Power supply monitoring**: tracks AC power status and provides the
|
||||
`sys/pwr/ac` condition
|
||||
- **Device conditions**: sets `dev/*` conditions in the Finit condition
|
||||
system when device nodes appear or disappear
|
||||
- **Netlink rebroadcast**: rebroadcasts processed uevents to netlink
|
||||
group 0x4 for [libudev-zero][] consumers (enabled by default)
|
||||
- **D-Bus interface**: `org.finit.Device1` -- settle, trigger, device
|
||||
info, rules reload, and queue state over its own bus socket
|
||||
|
||||
|
||||
Device Nodes
|
||||
------------
|
||||
|
||||
On receiving an `add` event with `MAJOR`, `MINOR`, and `DEVNAME`
|
||||
fields, keventd creates the corresponding device node in `/dev` using
|
||||
`mknod()`. Parent directories are created automatically (e.g.,
|
||||
`/dev/input/` for `/dev/input/event0`).
|
||||
|
||||
On `remove` events, the device node and its associated symlinks and
|
||||
conditions are cleaned up.
|
||||
|
||||
### Default Permissions
|
||||
|
||||
keventd applies permissions based on built-in rules that match on
|
||||
device subsystem and name:
|
||||
|
||||
| **Subsystem** | **Pattern** | **Mode** | **Owner:Group** |
|
||||
|---------------|--------------------------------------------|----------|-----------------|
|
||||
| block | sd*, vd*, nvme*, mmcblk*, loop*, dm-*, md* | 0660 | root:disk |
|
||||
| tty | tty[0-9]* | 0620 | root:tty |
|
||||
| tty | ttyS*, ttyUSB*, ttyACM* | 0660 | root:dialout |
|
||||
| input | event*, mouse*, mice | 0660 | root:root |
|
||||
| sound | * | 0660 | root:audio |
|
||||
| video4linux | * | 0660 | root:video |
|
||||
| drm | card*, render* | 0660 | root:video |
|
||||
| (any) | null, zero, full, random, urandom | 0666 | root:root |
|
||||
| (any) | console | 0600 | root:root |
|
||||
| (default) | | 0660 | root:root |
|
||||
|
||||
|
||||
Persistent Symlinks
|
||||
-------------------
|
||||
|
||||
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, under `/dev/input/`:
|
||||
|
||||
- **by-id**: based on the device name from sysfs
|
||||
- **by-path**: based on the physical device path
|
||||
|
||||
The rest come from `SYMLINK+=` in the udev rules, so what you get
|
||||
depends on the ruleset installed. The curated rules Finit ships add,
|
||||
among others:
|
||||
|
||||
| **Directory** | **Links** |
|
||||
|----------------|--------------------------------------------------------|
|
||||
| `/dev/disk/` | `by-uuid`, `by-label`, `by-partuuid`, `by-partlabel`, `by-diskseq`, and further `by-id` and `by-path` names for NVMe, virtio, MMC, WWN, and FireWire |
|
||||
| `/dev/input/` | longer `by-id` and `by-path` names than the built-in ones, down to the USB interface number |
|
||||
| `/dev/serial/` | `by-id`, `by-path` |
|
||||
| `/dev/snd/` | `by-id`, `by-path` |
|
||||
| `/dev/v4l/` | `by-id`, `by-path` |
|
||||
| `/dev/tape/` | `by-id`, `by-path` |
|
||||
| `/dev/dri/` | `by-path` |
|
||||
|
||||
They also set up a few fixed names: `/dev/rtc`, `/dev/cdrom`, and
|
||||
`/dev/virtio-ports/<name>`.
|
||||
|
||||
The `by-uuid` and `by-label` links are why keventd needs libblkid. The
|
||||
rules read the filesystem metadata off the device with
|
||||
`IMPORT{builtin}="blkid"`, and libblkid is what that builtin calls.
|
||||
|
||||
Built-in and rule-provided symlinks alike are tracked internally and
|
||||
removed when the corresponding device is unplugged.
|
||||
|
||||
|
||||
Firmware Loading
|
||||
----------------
|
||||
|
||||
When a kernel driver requests firmware (via `request_firmware()`), the
|
||||
kernel sends a uevent with a `FIRMWARE=` field. keventd handles this
|
||||
by:
|
||||
|
||||
1. Searching for the firmware file in order:
|
||||
- `/lib/firmware/updates/<kernel-version>/<name>`
|
||||
- `/lib/firmware/updates/<name>`
|
||||
- `/lib/firmware/<kernel-version>/<name>`
|
||||
- `/lib/firmware/<name>`
|
||||
2. Writing `1` to `/sys/<devpath>/loading` to signal start
|
||||
3. Copying the firmware data to `/sys/<devpath>/data`
|
||||
4. Writing `0` to `/sys/<devpath>/loading` on success (or `-1` on failure)
|
||||
|
||||
This is particularly important early in boot when drivers for graphics
|
||||
cards, network adapters, and other hardware need firmware before they
|
||||
can operate.
|
||||
|
||||
|
||||
Module Loading
|
||||
--------------
|
||||
|
||||
When a device add event includes a `MODALIAS` field, keventd spawns
|
||||
`modprobe -bq <modalias>` to load the matching kernel module. Module
|
||||
loading is done asynchronously (keventd does not wait for modprobe to
|
||||
complete) to avoid blocking other event processing.
|
||||
|
||||
|
||||
Coldplug
|
||||
--------
|
||||
|
||||
To handle devices that were present before keventd started, it supports
|
||||
a coldplug mode activated with the `-c` flag. This walks the entire
|
||||
`/sys/devices` tree and writes `add` to each `uevent` file, causing the
|
||||
kernel to re-emit add events for all existing devices.
|
||||
|
||||
This replaces the separate `coldplug` script previously used with mdev.
|
||||
|
||||
When `-c` is used, keventd defers its pidfile (and the `pid/keventd`
|
||||
condition Finit asserts from it) until the coldplug event queue has
|
||||
been fully drained. Services that depend on `<pid/keventd>` can
|
||||
therefore assume `/dev` is populated and persistent symlinks are live,
|
||||
without needing a separate `settle` step.
|
||||
|
||||
|
||||
Netlink Rebroadcast
|
||||
-------------------
|
||||
|
||||
The Linux kernel sends uevents to netlink multicast group 1 (bit 0) of
|
||||
`NETLINK_KOBJECT_UEVENT`. Only the device manager listens on this raw
|
||||
kernel group. Userspace consumers — applications using libudev —
|
||||
expect to receive processed events on a separate netlink group.
|
||||
|
||||
systemd/udevd established the convention of rebroadcasting processed
|
||||
events to a separate group, and [libudev-zero][], a daemonless drop-in
|
||||
replacement for libudev, listens on group `0x4` for these events.
|
||||
Without a device manager rebroadcasting, graphical applications,
|
||||
Wayland/X11 compositors, libinput, and anything else using libudev to
|
||||
monitor device hotplug will never see any events.
|
||||
|
||||
keventd rebroadcasts by default to netlink group 4 (`0x4`). A second
|
||||
netlink socket is created at startup, and after each uevent has been
|
||||
fully processed (device nodes created, symlinks set up, modules loaded),
|
||||
the original raw event is sent to the configured group(s).
|
||||
Rebroadcasting after processing ensures that device nodes and symlinks
|
||||
already exist by the time consumers receive the notification.
|
||||
|
||||
Use `-g GROUP` to override the default group mask, or `-G` to disable
|
||||
rebroadcast entirely. Bit 0 of the group mask is always forced off to
|
||||
prevent a feedback loop with the kernel's own multicast group.
|
||||
|
||||
### Background
|
||||
|
||||
The netlink uevent architecture uses separate multicast groups to
|
||||
isolate the kernel-to-device-manager channel from the device-manager-to-
|
||||
application channel:
|
||||
|
||||
| **Group** | **Bit** | **Purpose** |
|
||||
|-----------|---------|---------------------------------------------|
|
||||
| 1 | 0 | Kernel events (device manager listens here) |
|
||||
| 4 | 2 | Processed events (libudev consumers listen) |
|
||||
|
||||
This two-group design was established by systemd/udevd and is the de
|
||||
facto standard. [mdevd][] implements the same mechanism via its `-O`
|
||||
flag, and keventd follows the same convention.
|
||||
|
||||
For more details, see:
|
||||
|
||||
- [libudev-zero][] — daemonless replacement for libudev
|
||||
- [mdevd][] — mdev-compatible device manager with rebroadcast support
|
||||
|
||||
|
||||
Conditions
|
||||
----------
|
||||
|
||||
keventd provides conditions in two namespaces:
|
||||
|
||||
### Device Conditions (`dev/`)
|
||||
|
||||
When a device node is created, keventd asserts a corresponding condition
|
||||
in `/run/finit/cond/dev/`. This allows services to wait for specific
|
||||
devices:
|
||||
|
||||
service mdadm {
|
||||
description = "RAID monitor"
|
||||
runlevel = "2345"
|
||||
conditions = { "dev/sda" }
|
||||
command = "/usr/sbin/mdadm --monitor /dev/md0"
|
||||
}
|
||||
|
||||
service gps-daemon {
|
||||
description = "GPS daemon"
|
||||
runlevel = "2345"
|
||||
conditions = { "dev/ttyUSB0" }
|
||||
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
|
||||
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/`)
|
||||
|
||||
keventd monitors the `power_supply` subsystem and provides:
|
||||
|
||||
- `sys/pwr/ac` — asserted when AC power is connected
|
||||
|
||||
This is useful for preventing power-hungry services from running on
|
||||
battery:
|
||||
|
||||
service cron {
|
||||
description = "Cron daemon"
|
||||
runlevel = "2345"
|
||||
conditions = { "sys/pwr/ac", "pid/syslogd" }
|
||||
command = "cron -f"
|
||||
}
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
keventd [-cdGhnpSv] [-g GROUP] [-r DIR] [-t SECONDS]
|
||||
|
||||
Options:
|
||||
-c Run coldplug at startup
|
||||
-d Enable debug mode (foreground, verbose)
|
||||
-g GROUP Override netlink rebroadcast group (default: 4)
|
||||
-G Disable netlink rebroadcast entirely
|
||||
-h Show help text
|
||||
-n Run in foreground (no daemon)
|
||||
-p Passive mode: power supply events only
|
||||
-r DIR Extra rules directory
|
||||
-S Settle mode: wait for kernel uevent queue to quiet, then exit
|
||||
-t SEC Settle timeout (default 30s, used with -S)
|
||||
-v Show version
|
||||
|
||||
In normal operation, Finit starts keventd automatically via its system
|
||||
configuration. The `-d` flag is useful for debugging device issues --
|
||||
it runs keventd in the foreground and logs all received uevents.
|
||||
|
||||
`keventd -S` is a one-shot command, not a flag to the running daemon.
|
||||
It is the `udevadm settle` equivalent for migration scenarios:
|
||||
|
||||
keventd -S -t 10 && start-graphical-session
|
||||
|
||||
With D-Bus support it asks the running keventd over
|
||||
[`Device1.Settle`](#d-bus-interface-orgfinitdevice1), which tracks the
|
||||
event queue where the events actually flow. When no bus answers --
|
||||
another device manager, or a build without D-Bus -- it falls back to
|
||||
polling `/sys/kernel/uevent_seqnum` until the kernel's sequence
|
||||
counter has been stable for 200ms, then exits zero. After the
|
||||
`-t SECONDS` timeout (default 30) it exits non-zero instead. The
|
||||
fallback works regardless of which device manager is active, or even
|
||||
if none is.
|
||||
|
||||
Prefer the condition-based model (`<dev/X>`, `<class/...>`,
|
||||
`<driver/...>`) over settle when you control the service definition --
|
||||
settle is racy with slow probes that fire after the queue appears
|
||||
quiet. It is provided for legacy boot scripts and init transitions
|
||||
where condition wiring isn't feasible.
|
||||
|
||||
Debug logging can also be toggled at runtime by sending `SIGUSR1`:
|
||||
|
||||
kill -USR1 $(pidof keventd)
|
||||
|
||||
|
||||
D-Bus Interface (`org.finit.Device1`)
|
||||
-------------------------------------
|
||||
|
||||
With D-Bus support (default, `--disable-dbus` opts out) keventd serves
|
||||
`org.finit.Device1` at `/org/finit/device` on its own brokerless bus,
|
||||
`unix:path=/run/keventd/bus`, the same way Finit serves
|
||||
[`org.finit`](dbus.md) on `/run/finit/bus`. There is no forwarding
|
||||
between the two -- clients that want both connect to both.
|
||||
|
||||
| Method | In sig | Out sig | Priv. | Notes |
|
||||
|---------------|--------|---------|-------|----------------------------------------------|
|
||||
| `Settle` | `u` | `b` | no | Wait until the event queue drains, timeout in seconds; `false` on timeout |
|
||||
| `Trigger` | `ss` | — | yes | Replay events: action, subsystem glob (empty = all) |
|
||||
| `Info` | `s` | `a{ss}` | no | `/run/udev/data` properties for a devpath |
|
||||
| `RulesReload` | — | `u` | yes | Re-read the rules directories, returns rule count |
|
||||
|
||||
| Property | Sig | Notes |
|
||||
|-------------------|-----|-------------------------------------------|
|
||||
| `QueueEmpty` | `b` | No device events in flight |
|
||||
| `SeqnumProcessed` | `t` | Highest kernel seqnum keventd has handled |
|
||||
|
||||
| Signal | Body | Fires when |
|
||||
|-------------------|-------------------------|---------------------------------|
|
||||
| `DeviceProcessed` | `ss` — devpath, action | An event has been fully handled: node, symlinks, database |
|
||||
|
||||
Example, wait up to ten seconds for the queue to drain:
|
||||
|
||||
```sh
|
||||
dbus-send --address=unix:path=/run/keventd/bus \
|
||||
--type=method_call --print-reply --dest=org.finit \
|
||||
/org/finit/device org.finit.Device1.Settle uint32:10
|
||||
```
|
||||
|
||||
In passive mode (`-p`) `Trigger` and `RulesReload` refuse -- device
|
||||
events are not handled here -- while `Settle` and the queue-state
|
||||
properties remain meaningful.
|
||||
|
||||
keventd's conditions are symlinks to Finit's `reconf` generation
|
||||
marker, so they read the current generation by construction and never
|
||||
enter `flux` -- like user-defined conditions, they need no reassert
|
||||
after `initctl reload`. Device state does not change because Finit
|
||||
re-read its configuration.
|
||||
|
||||
|
||||
Integration with Finit
|
||||
----------------------
|
||||
|
||||
keventd is a standalone daemon started by Finit as an internal service.
|
||||
It communicates with Finit exclusively through the filesystem-based
|
||||
condition system — creating and removing symlinks in `/run/finit/cond/`.
|
||||
|
||||
This means keventd can also be tested independently:
|
||||
|
||||
# Run in debug mode to see all kernel events
|
||||
keventd -d
|
||||
|
||||
# Run with coldplug to populate /dev from scratch
|
||||
keventd -c -n
|
||||
|
||||
# Run without rebroadcast (e.g., headless embedded system)
|
||||
keventd -c -G
|
||||
|
||||
Only one device manager should be active at a time. This is settled
|
||||
at build time: with the hotplug plugin enabled, Finit starts keventd
|
||||
in passive mode (`-p`), leaving device management to the udevd, mdevd,
|
||||
or mdev service from `system/10-hotplug.conf` and monitoring only
|
||||
power supply events. Without the hotplug plugin, keventd runs as the
|
||||
system device manager (`keventd -c`).
|
||||
|
||||
[libudev-zero]: https://github.com/illiliti/libudev-zero
|
||||
[mdevd]: https://skarnet.org/software/mdevd/
|
||||
@@ -40,6 +40,11 @@ For your convenience a set of *optional* plugins are available:
|
||||
`tmpfiles.d/dbus.conf`, both overridable from `/etc`; the plugin
|
||||
itself only handles what has to inspect the running system.
|
||||
|
||||
Not to be confused with Finit's built-in [D-Bus API](dbus.md): the
|
||||
plugin (`--disable-dbus-plugin`) starts the external system bus
|
||||
daemon, while the built-in support (`--disable-dbus`) is the
|
||||
`org.finit` interface served by Finit itself.
|
||||
|
||||
* *hook-scripts.so*: Trigger the execution of scripts from plugin hook
|
||||
points (see [Hooks](#hooks)). _Optional plugin._
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,4 @@
|
||||
.deps/
|
||||
keventd
|
||||
Makefile
|
||||
Makefile.in
|
||||
@@ -0,0 +1,67 @@
|
||||
AM_CPPFLAGS = -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_GNU_SOURCE -D_DEFAULT_SOURCE
|
||||
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
|
||||
# Per-target flags exist even when their conditional is false, so
|
||||
# assign the base unconditionally and append under DBUS
|
||||
keventd_CPPFLAGS = $(AM_CPPFLAGS)
|
||||
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 += $(uev_CFLAGS)
|
||||
keventd_CFLAGS += $(blkid_CFLAGS)
|
||||
keventd_LDADD = $(lite_LIBS)
|
||||
keventd_LDADD += $(uev_LIBS)
|
||||
keventd_LDADD += $(blkid_LIBS)
|
||||
|
||||
if DBUS
|
||||
keventd_SOURCES += dbus.c
|
||||
keventd_CPPFLAGS += -I$(top_srcdir)/libink
|
||||
keventd_LDADD += $(top_builddir)/libink/libink.la
|
||||
endif
|
||||
|
||||
# Curated udev rules derived from eudev 3.2.14 (GPL-2.0-or-later).
|
||||
# Installed to /lib/udev/rules.d/ so existing third-party rules and
|
||||
# local overrides under /etc/udev/rules.d/ coexist with standard udev
|
||||
# precedence. See .notes/curated-rules-manifest.md.
|
||||
if UDEV_RULES
|
||||
udevrulesdir = $(prefix)/lib/udev/rules.d
|
||||
dist_udevrules_DATA = \
|
||||
rules.d/50-udev-default.rules \
|
||||
rules.d/60-autosuspend.rules \
|
||||
rules.d/60-block.rules \
|
||||
rules.d/60-cdrom_id.rules \
|
||||
rules.d/60-drm.rules \
|
||||
rules.d/60-evdev.rules \
|
||||
rules.d/60-fido-id.rules \
|
||||
rules.d/60-input-id.rules \
|
||||
rules.d/60-persistent-alsa.rules \
|
||||
rules.d/60-persistent-input.rules \
|
||||
rules.d/60-persistent-storage.rules \
|
||||
rules.d/60-persistent-storage-tape.rules \
|
||||
rules.d/60-persistent-v4l.rules \
|
||||
rules.d/60-sensor.rules \
|
||||
rules.d/60-serial.rules \
|
||||
rules.d/64-btrfs.rules \
|
||||
rules.d/70-camera.rules \
|
||||
rules.d/70-joystick.rules \
|
||||
rules.d/70-memory.rules \
|
||||
rules.d/70-mouse.rules \
|
||||
rules.d/70-touchpad.rules \
|
||||
rules.d/75-net-description.rules \
|
||||
rules.d/75-probe_mtd.rules \
|
||||
rules.d/78-sound-card.rules \
|
||||
rules.d/80-drivers.rules \
|
||||
rules.d/80-net-name-slot.rules \
|
||||
rules.d/81-net-dhcp.rules
|
||||
endif
|
||||
+1529
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:
|
||||
*/
|
||||
+577
@@ -0,0 +1,577 @@
|
||||
/* keventd D-Bus interface: org.finit.Device1 on its own local bus
|
||||
*
|
||||
* keventd serves /run/keventd/bus the same way Finit serves
|
||||
* /run/finit/bus: brokerless, libink, one socket per daemon. No
|
||||
* message forwarding exists between the two, by design -- clients
|
||||
* that want both connect to both.
|
||||
*
|
||||
* Copyright (c) 2026 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 "config.h"
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <uev/uev.h>
|
||||
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
#else
|
||||
# include <lite/lite.h>
|
||||
#endif
|
||||
|
||||
#include "link.h"
|
||||
|
||||
#include "keventd.h"
|
||||
#include "udevdb.h"
|
||||
#include "util.h"
|
||||
|
||||
#define DEVBUS_DIR "/run/keventd"
|
||||
#define DEVBUS_SOCKET DEVBUS_DIR "/bus"
|
||||
#define DEVBUS_PATH_OBJECT "/org/finit/device"
|
||||
#define DEVBUS_INTERFACE "org.finit.Device1"
|
||||
#define DEVBUS_MAX_PEERS 16
|
||||
#define DEVBUS_MAX_SETTLES 8
|
||||
|
||||
struct peer {
|
||||
uev_t watcher;
|
||||
link_connection_t *conn;
|
||||
int dead;
|
||||
TAILQ_ENTRY(peer) link;
|
||||
};
|
||||
|
||||
static TAILQ_HEAD(, peer) peers = TAILQ_HEAD_INITIALIZER(peers);
|
||||
static TAILQ_HEAD(, peer) reapq = TAILQ_HEAD_INITIALIZER(reapq);
|
||||
static link_server_t *server;
|
||||
static uev_t accept_watcher;
|
||||
static uev_t reap_timer;
|
||||
static uev_ctx_t *devbus_ctx;
|
||||
static size_t peer_count;
|
||||
static int bus_passive;
|
||||
|
||||
/* Parked Settle() calls with their own deadlines */
|
||||
struct settle {
|
||||
link_connection_t *conn;
|
||||
link_authz_t tok;
|
||||
uint64_t deadline; /* CLOCK_MONOTONIC ms */
|
||||
};
|
||||
|
||||
static struct settle settles[DEVBUS_MAX_SETTLES];
|
||||
static uev_t settle_timer;
|
||||
static int settle_armed;
|
||||
|
||||
/*
|
||||
* Same policy as /run/finit/bus: root and members of the --with-group
|
||||
* group. The group set comes from the kernel (SO_PEERGROUPS), the
|
||||
* owning gid is resolved once at init so no call ever hits NSS.
|
||||
*/
|
||||
static gid_t privileged_gid = (gid_t)-1; /* DEFGROUP, resolved at init */
|
||||
|
||||
static int caller_is_privileged(uid_t uid, const gid_t *groups, int ngroups,
|
||||
void *userdata)
|
||||
{
|
||||
(void)userdata;
|
||||
|
||||
if (uid == 0)
|
||||
return 1;
|
||||
|
||||
if (privileged_gid == (gid_t)-1)
|
||||
return 0; /* no owning group to match against */
|
||||
|
||||
for (int i = 0; i < ngroups; i++) {
|
||||
if (groups[i] == privileged_gid)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void link_log_cb(void *userdata, const char *func, const char *msg)
|
||||
{
|
||||
(void)userdata;
|
||||
logit(LOG_DEBUG, "%s():%s", func, msg);
|
||||
}
|
||||
|
||||
/* ---------- peer lifecycle, same shape as src/dbus.c ---------- */
|
||||
|
||||
static void peer_reap(uev_t *w, void *arg, int events)
|
||||
{
|
||||
struct peer *p;
|
||||
|
||||
(void)w; (void)arg; (void)events;
|
||||
while ((p = TAILQ_FIRST(&reapq))) {
|
||||
TAILQ_REMOVE(&reapq, p, link);
|
||||
link_connection_close(p->conn);
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
|
||||
static void peer_drop(struct peer *p)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (p->dead)
|
||||
return;
|
||||
p->dead = 1;
|
||||
|
||||
uev_io_stop(&p->watcher);
|
||||
TAILQ_REMOVE(&peers, p, link);
|
||||
peer_count--;
|
||||
|
||||
/* Settles parked on this connection die with it */
|
||||
for (i = 0; i < DEVBUS_MAX_SETTLES; i++) {
|
||||
if (settles[i].conn == p->conn)
|
||||
settles[i].conn = NULL;
|
||||
}
|
||||
|
||||
TAILQ_INSERT_TAIL(&reapq, p, link);
|
||||
uev_timer_set(&reap_timer, 10, 0);
|
||||
}
|
||||
|
||||
static void peer_cb(uev_t *w, void *arg, int events)
|
||||
{
|
||||
struct peer *p = arg;
|
||||
|
||||
(void)w;
|
||||
|
||||
if (UEV_ERROR == events || link_connection_process(p->conn) < 0)
|
||||
peer_drop(p);
|
||||
}
|
||||
|
||||
static void accept_cb(uev_t *w, void *arg, int events)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
if (UEV_ERROR == events) {
|
||||
logit(LOG_ERR, "D-Bus accept watcher error");
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
link_connection_t *conn = NULL;
|
||||
struct peer *p;
|
||||
|
||||
if (link_server_accept(server, &conn) < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK)
|
||||
logit(LOG_ERR, "Failed accepting D-Bus client: %s",
|
||||
strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
if (peer_count >= DEVBUS_MAX_PEERS) {
|
||||
logit(LOG_WARNING, "D-Bus peer cap reached (%zu), dropping",
|
||||
peer_count);
|
||||
link_connection_close(conn);
|
||||
continue;
|
||||
}
|
||||
|
||||
p = calloc(1, sizeof(*p));
|
||||
if (!p) {
|
||||
link_connection_close(conn);
|
||||
continue;
|
||||
}
|
||||
|
||||
p->conn = conn;
|
||||
TAILQ_INSERT_TAIL(&peers, p, link);
|
||||
peer_count++;
|
||||
|
||||
if (uev_io_init(w->ctx, &p->watcher, peer_cb, p,
|
||||
link_connection_get_fd(conn), UEV_READ))
|
||||
peer_drop(p);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- parked Settle() bookkeeping ---------- */
|
||||
|
||||
static void settle_sweep(uev_t *w, void *arg, int events);
|
||||
|
||||
/*
|
||||
* The queue can only drain when an event is processed, and
|
||||
* devbus_notify() sweeps on exactly that; the timer exists solely to
|
||||
* enforce deadlines, so arm it one-shot for the nearest one.
|
||||
*/
|
||||
static void settle_arm(uint64_t deadline)
|
||||
{
|
||||
uint64_t now = kev_now_ms();
|
||||
int ms = deadline > now ? (int)(deadline - now) : 1;
|
||||
|
||||
if (!settle_armed) {
|
||||
uev_timer_init(devbus_ctx, &settle_timer, settle_sweep,
|
||||
NULL, ms, 0);
|
||||
settle_armed = 1;
|
||||
} else {
|
||||
uev_timer_set(&settle_timer, ms, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Resume parked settles: all of them when the queue drained (the
|
||||
* handler re-runs, sees the empty queue, replies true), expired ones
|
||||
* otherwise (the handler re-runs, sees link_call_resumed(), replies
|
||||
* false).
|
||||
*/
|
||||
static void settle_sweep(uev_t *w, void *arg, int events)
|
||||
{
|
||||
uint64_t next = 0;
|
||||
uint64_t now = kev_now_ms();
|
||||
int empty = kev_queue_empty();
|
||||
int i;
|
||||
|
||||
(void)w; (void)arg; (void)events;
|
||||
|
||||
for (i = 0; i < DEVBUS_MAX_SETTLES; i++) {
|
||||
struct settle *s = &settles[i];
|
||||
|
||||
if (!s->conn)
|
||||
continue;
|
||||
|
||||
if (empty || now >= s->deadline) {
|
||||
link_connection_t *conn = s->conn;
|
||||
link_authz_t tok = s->tok;
|
||||
|
||||
s->conn = NULL;
|
||||
link_call_resume(conn, tok);
|
||||
continue;
|
||||
}
|
||||
if (!next || s->deadline < next)
|
||||
next = s->deadline;
|
||||
}
|
||||
|
||||
if (next) {
|
||||
settle_arm(next);
|
||||
} else if (settle_armed) {
|
||||
uev_timer_stop(&settle_timer);
|
||||
settle_armed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- org.finit.Device1 ---------- */
|
||||
|
||||
static int device1_settle(link_call_t *call, void *u)
|
||||
{
|
||||
link_writer_t *w;
|
||||
uint32_t timeout;
|
||||
int i;
|
||||
|
||||
int empty;
|
||||
|
||||
(void)u;
|
||||
if (link_call_read_u32(call, &timeout) < 0)
|
||||
return link_call_reply_error(call,
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"expected (u)");
|
||||
|
||||
empty = kev_queue_empty();
|
||||
if (!empty && !link_call_resumed(call)) {
|
||||
for (i = 0; i < DEVBUS_MAX_SETTLES; i++) {
|
||||
if (settles[i].conn)
|
||||
continue;
|
||||
|
||||
if (link_call_park(call, &settles[i].tok))
|
||||
break;
|
||||
|
||||
settles[i].conn = link_call_connection(call);
|
||||
settles[i].deadline = kev_now_ms() + (uint64_t)timeout * 1000;
|
||||
settle_arm(settles[i].deadline);
|
||||
return 0;
|
||||
}
|
||||
/* no room to wait: answer with the state as it stands */
|
||||
}
|
||||
|
||||
w = link_call_reply(call);
|
||||
link_w_bool(w, empty);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int device1_trigger(link_call_t *call, void *u)
|
||||
{
|
||||
const char *action, *glob;
|
||||
|
||||
(void)u;
|
||||
if (link_call_read_string(call, &action) < 0 ||
|
||||
link_call_read_string(call, &glob) < 0)
|
||||
return link_call_reply_error(call,
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"expected (s, s)");
|
||||
|
||||
if (bus_passive)
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.Failed",
|
||||
"keventd is passive, device events are not handled here");
|
||||
|
||||
if (*action && !uevent_action_valid(action))
|
||||
return link_call_reply_error(call,
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"unknown uevent action");
|
||||
|
||||
if (coldplug_trigger(action, glob))
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.Failed", "trigger failed");
|
||||
|
||||
(void)link_call_reply(call);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int device1_info(link_call_t *call, void *u)
|
||||
{
|
||||
struct uevent ev;
|
||||
const char *devpath;
|
||||
link_writer_t *w;
|
||||
int i;
|
||||
|
||||
(void)u;
|
||||
if (link_call_read_string(call, &devpath) < 0 || *devpath != '/')
|
||||
return link_call_reply_error(call,
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"expected (s), an absolute devpath");
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
if (udevdb_read_devpath(devpath, &ev)) {
|
||||
uevent_env_free(&ev);
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.NoSuchDevice",
|
||||
"no database record for devpath");
|
||||
}
|
||||
|
||||
w = link_call_reply(call);
|
||||
link_w_array_begin(w, '{');
|
||||
for (i = 0; i < ev.nenv; i++) {
|
||||
link_w_struct_begin(w);
|
||||
link_w_string(w, ev.env_key[i]);
|
||||
link_w_string(w, ev.env_val[i]);
|
||||
link_w_struct_end(w);
|
||||
}
|
||||
link_w_array_end(w);
|
||||
|
||||
uevent_env_free(&ev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int device1_rules_reload(link_call_t *call, void *u)
|
||||
{
|
||||
link_writer_t *w;
|
||||
|
||||
(void)u;
|
||||
if (bus_passive)
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.Failed",
|
||||
"keventd is passive, rules are not applied here");
|
||||
|
||||
w = link_call_reply(call);
|
||||
link_w_u32(w, (uint32_t)kev_rules_reload());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int prop_queue_empty(link_writer_t *w, void *u)
|
||||
{
|
||||
(void)u;
|
||||
link_w_bool(w, kev_queue_empty());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int prop_seqnum(link_writer_t *w, void *u)
|
||||
{
|
||||
(void)u;
|
||||
link_w_u64(w, kev_seq_processed());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const link_method_t device_methods[] = {
|
||||
{ .name = "Settle", .in_sig = "u", .out_sig = "b",
|
||||
.handler = device1_settle },
|
||||
{ .name = "Trigger", .in_sig = "ss", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = device1_trigger },
|
||||
{ .name = "Info", .in_sig = "s", .out_sig = "a{ss}",
|
||||
.handler = device1_info },
|
||||
{ .name = "RulesReload", .in_sig = "", .out_sig = "u",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = device1_rules_reload },
|
||||
{ NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static const link_property_t device_properties[] = {
|
||||
{ .name = "QueueEmpty", .sig = "b", .getter = prop_queue_empty },
|
||||
{ .name = "SeqnumProcessed", .sig = "t", .getter = prop_seqnum },
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
static const link_signal_t device_signals[] = {
|
||||
{ .name = "DeviceProcessed", .sig = "ss" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const link_vtable_t device_vtable = {
|
||||
.interface = DEVBUS_INTERFACE,
|
||||
.methods = device_methods,
|
||||
.properties = device_properties,
|
||||
.signals = device_signals,
|
||||
};
|
||||
|
||||
/* ---------- signal emission + settle nudge ---------- */
|
||||
|
||||
static void devbus_emit_signal(const char *member, const char *sig,
|
||||
const uint8_t *body, size_t len)
|
||||
{
|
||||
struct peer *p, *tmp;
|
||||
|
||||
TAILQ_FOREACH_SAFE(p, &peers, link, tmp) {
|
||||
if (link_connection_emit_signal(p->conn, DEVBUS_PATH_OBJECT,
|
||||
DEVBUS_INTERFACE, member,
|
||||
sig, body, len) >= 0)
|
||||
continue;
|
||||
if (errno == EMSGSIZE || errno == EINVAL)
|
||||
break; /* nothing on the wire, same for all */
|
||||
peer_drop(p);
|
||||
}
|
||||
}
|
||||
|
||||
void devbus_notify(const char *devpath, const char *action)
|
||||
{
|
||||
uint8_t body[512];
|
||||
link_writer_t w;
|
||||
ssize_t blen;
|
||||
|
||||
if (!server)
|
||||
return;
|
||||
|
||||
/* A processed event may have been the last one in flight */
|
||||
if (settle_armed)
|
||||
settle_sweep(NULL, NULL, 0);
|
||||
|
||||
if (TAILQ_EMPTY(&peers) || !devpath)
|
||||
return;
|
||||
|
||||
link_writer_init(&w, body, sizeof(body));
|
||||
link_w_string(&w, devpath);
|
||||
link_w_string(&w, action ?: "");
|
||||
blen = link_writer_finish(&w);
|
||||
if (blen < 0)
|
||||
return;
|
||||
|
||||
devbus_emit_signal("DeviceProcessed", "ss", body, (size_t)blen);
|
||||
}
|
||||
|
||||
/* ---------- init / exit / settle client ---------- */
|
||||
|
||||
int devbus_init(uev_ctx_t *ctx, int passive)
|
||||
{
|
||||
devbus_ctx = ctx;
|
||||
bus_passive = passive;
|
||||
|
||||
link_set_logger(link_log_cb, NULL);
|
||||
|
||||
if (mkpath(DEVBUS_DIR, 0755) && errno != EEXIST) {
|
||||
logit(LOG_ERR, "Failed creating %s: %s", DEVBUS_DIR,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (link_server_new(&server, DEVBUS_SOCKET, 0660) < 0) {
|
||||
logit(LOG_ERR, "Failed creating D-Bus socket %s: %s",
|
||||
DEVBUS_SOCKET, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Resolve the owning group once, see caller_is_privileged() */
|
||||
privileged_gid = getgroup(DEFGROUP);
|
||||
if (chown(DEVBUS_SOCKET, geteuid(), privileged_gid))
|
||||
logit(LOG_WARNING, "Failed setting group %s on %s: %s",
|
||||
DEFGROUP, DEVBUS_SOCKET, strerror(errno));
|
||||
|
||||
link_server_set_authorizer(server, caller_is_privileged, NULL);
|
||||
|
||||
if (link_server_add_object(server, DEVBUS_PATH_OBJECT,
|
||||
&device_vtable, NULL) < 0) {
|
||||
logit(LOG_ERR, "Failed registering %s", DEVBUS_INTERFACE);
|
||||
link_server_free(server);
|
||||
server = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
uev_timer_init(ctx, &reap_timer, peer_reap, NULL, 0, 0);
|
||||
|
||||
if (uev_io_init(ctx, &accept_watcher, accept_cb, NULL,
|
||||
link_server_get_fd(server), UEV_READ)) {
|
||||
logit(LOG_ERR, "Failed watching D-Bus socket");
|
||||
link_server_free(server);
|
||||
server = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
logit(LOG_NOTICE, "Serving %s on %s", DEVBUS_INTERFACE, DEVBUS_SOCKET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void devbus_exit(void)
|
||||
{
|
||||
struct peer *p;
|
||||
|
||||
if (!server)
|
||||
return;
|
||||
|
||||
while ((p = TAILQ_FIRST(&peers)))
|
||||
peer_drop(p);
|
||||
peer_reap(NULL, NULL, 0);
|
||||
|
||||
uev_io_stop(&accept_watcher);
|
||||
link_server_free(server);
|
||||
server = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Client side of Settle, for `keventd -S`: ask the running keventd.
|
||||
* Returns 0 settled, 1 timeout, -1 when no bus answers (fall back to
|
||||
* polling the kernel seqnum).
|
||||
*/
|
||||
int devbus_client_settle(int timeout_s)
|
||||
{
|
||||
link_client_t *c;
|
||||
link_reader_t r;
|
||||
const link_reply_t *reply;
|
||||
int rc, settled = 0;
|
||||
|
||||
c = link_client_open(DEVBUS_SOCKET);
|
||||
if (!c)
|
||||
return -1;
|
||||
|
||||
rc = link_client_call_v(c, DEVBUS_PATH_OBJECT, DEVBUS_INTERFACE,
|
||||
"Settle", "u", (uint32_t)timeout_s);
|
||||
if (rc != LINK_CALL_OK) {
|
||||
link_client_close(c);
|
||||
return -1;
|
||||
}
|
||||
|
||||
reply = link_client_reply(c);
|
||||
if (reply && reply->body) {
|
||||
link_reader_init(&r, reply->body, reply->body_len);
|
||||
link_r_bool(&r, &settled);
|
||||
}
|
||||
link_client_close(c);
|
||||
|
||||
return settled ? 0 : 1;
|
||||
}
|
||||
|
||||
#endif /* HAVE_DBUS */
|
||||
@@ -0,0 +1,851 @@
|
||||
/* Unified device manager - kernel events, device nodes, symlinks, conditions
|
||||
*
|
||||
* TODO / known limitations
|
||||
* - firmware_load() copies synchronously in the main event loop; a
|
||||
* multi-MB blob (e.g. iwlwifi) blocks all other event handling for
|
||||
* the duration. Worker pool / fork-per-event would unblock this
|
||||
* (see .notes/keventd-eudev-gap-analysis.md, gap #8).
|
||||
* - firmware_load() does not handle compressed firmware (.xz, .zst).
|
||||
* Modern linux-firmware ships .xz blobs; kernels with
|
||||
* CONFIG_FW_LOADER_COMPRESS decompress in-kernel via direct
|
||||
* loading, but the legacy /sys/.../loading path used here passes
|
||||
* raw bytes only. Adding liblzma/libzstd would close this gap for
|
||||
* drivers using the userhelper fallback on systems without
|
||||
* CONFIG_FW_LOADER_COMPRESS.
|
||||
*
|
||||
* Copyright (c) 2021-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 "config.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/netlink.h>
|
||||
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
#else
|
||||
# include <lite/lite.h>
|
||||
#endif
|
||||
|
||||
#include <uev/uev.h>
|
||||
|
||||
#include "keventd.h"
|
||||
#include "rules.h"
|
||||
#include "sysfs.h"
|
||||
#include "udevdb.h"
|
||||
#include "cond.h"
|
||||
#include "pid.h"
|
||||
#include "util.h"
|
||||
|
||||
#define KEVENTD_VERSION "5.0"
|
||||
#define _PATH_SYSFS_PWR "/sys/class/power_supply"
|
||||
|
||||
/* Default netlink group for uevent rebroadcast (libudev-zero convention) */
|
||||
#define REBC_DEFAULT_NLGROUP 4
|
||||
|
||||
/* Used by settle and coldplug at startup */
|
||||
struct coldplug_gate {
|
||||
unsigned long long last_seq;
|
||||
uint64_t stamp; /* kev_now_ms() of last change */
|
||||
int primed;
|
||||
};
|
||||
|
||||
/*
|
||||
* Read the kernel's uevent sequence counter. Shared by the coldplug
|
||||
* gate, settle mode, and the D-Bus queue state.
|
||||
*/
|
||||
static int read_uevent_seqnum(unsigned long long *out)
|
||||
{
|
||||
FILE *fp;
|
||||
int rc;
|
||||
|
||||
fp = fopen("/sys/kernel/uevent_seqnum", "r");
|
||||
if (!fp)
|
||||
return -1;
|
||||
|
||||
rc = fscanf(fp, "%llu", out) == 1 ? 0 : -1;
|
||||
fclose(fp);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
uint64_t kev_now_ms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
/*
|
||||
* Track the kernel seqnum: returns 1 once it has been unchanged for
|
||||
* stable_ms, 0 while still moving (or before the first sample).
|
||||
*/
|
||||
static int seqnum_stable(struct coldplug_gate *cg, int stable_ms)
|
||||
{
|
||||
unsigned long long cur = 0;
|
||||
uint64_t now;
|
||||
|
||||
if (read_uevent_seqnum(&cur))
|
||||
return 0;
|
||||
|
||||
now = kev_now_ms();
|
||||
|
||||
if (!cg->primed || cur != cg->last_seq) {
|
||||
cg->last_seq = cur;
|
||||
cg->stamp = now;
|
||||
cg->primed = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return now - cg->stamp >= (uint64_t)stable_ms;
|
||||
}
|
||||
|
||||
static int num_ac_online;
|
||||
static int num_ac;
|
||||
|
||||
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 */
|
||||
|
||||
int debug; /* debug in other modules as well */
|
||||
|
||||
void logit(int prio, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
if (logon)
|
||||
vsyslog(prio, fmt, ap);
|
||||
else if (prio <= level) {
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
#define panic(fmt, args...) { logit(LOG_CRIT, fmt ":%s", ##args, strerror(errno)); exit(1); }
|
||||
#define warn(fmt, args...) { logit(LOG_WARNING, fmt ":%s", ##args, strerror(errno)); }
|
||||
|
||||
/*
|
||||
* Netlink rebroadcast support.
|
||||
*
|
||||
* The Linux kernel sends uevents to netlink multicast group 1 (bit 0)
|
||||
* of NETLINK_KOBJECT_UEVENT. Only the device manager should listen on
|
||||
* this raw kernel group. Userspace consumers (e.g., applications using
|
||||
* libudev) expect to receive processed events on a separate group --
|
||||
* conventionally group 4 (bit 2), established by systemd/udevd.
|
||||
*
|
||||
* libudev-zero (https://github.com/illiliti/libudev-zero), a daemonless
|
||||
* replacement for libudev, listens on group 0x4 for these rebroadcast
|
||||
* events. Without rebroadcast, graphical applications, Wayland/X11
|
||||
* compositors, libinput, and anything else using libudev to monitor
|
||||
* device hotplug will never see any events.
|
||||
*
|
||||
* Rebroadcast is enabled by default to group 0x4. Use -g to override
|
||||
* the group mask, or -G to disable rebroadcast entirely. Bit 0 is
|
||||
* always masked out to prevent a feedback loop with the kernel group.
|
||||
*/
|
||||
static int rebc_fd = -1;
|
||||
static unsigned int rebc_nlgroups;
|
||||
|
||||
static void rebc_init(unsigned int nlgroups)
|
||||
{
|
||||
/* Mask out bit 0 (kernel group) to prevent feedback loop */
|
||||
if (nlgroups & 1) {
|
||||
logit(LOG_WARNING, "rebroadcast group mask 0x%x includes kernel group (bit 0), masking it out", nlgroups);
|
||||
nlgroups &= ~1U;
|
||||
}
|
||||
if (!nlgroups) {
|
||||
logit(LOG_WARNING, "no valid rebroadcast groups remaining, rebroadcast disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
rebc_fd = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT);
|
||||
if (rebc_fd == -1) {
|
||||
warn("failed creating rebroadcast socket");
|
||||
return;
|
||||
}
|
||||
|
||||
rebc_nlgroups = nlgroups;
|
||||
logit(LOG_NOTICE, "rebroadcasting uevents to netlink group(s) 0x%x", nlgroups);
|
||||
}
|
||||
|
||||
static void rebc_event(char *buf, size_t len)
|
||||
{
|
||||
struct sockaddr_nl sa = { 0 };
|
||||
struct msghdr hdr = { 0 };
|
||||
struct iovec iov;
|
||||
|
||||
if (rebc_fd == -1)
|
||||
return;
|
||||
|
||||
iov.iov_base = buf;
|
||||
iov.iov_len = len;
|
||||
|
||||
sa.nl_family = AF_NETLINK;
|
||||
sa.nl_groups = rebc_nlgroups;
|
||||
|
||||
hdr.msg_name = &sa;
|
||||
hdr.msg_namelen = sizeof(sa);
|
||||
hdr.msg_iov = &iov;
|
||||
hdr.msg_iovlen = 1;
|
||||
|
||||
if (sendmsg(rebc_fd, &hdr, 0) == -1)
|
||||
logit(LOG_DEBUG, "rebroadcast failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
static int check_online(const char *online)
|
||||
{
|
||||
int val;
|
||||
|
||||
if (!online)
|
||||
return 0;
|
||||
|
||||
val = atoi(online);
|
||||
logit(LOG_INFO, "AC %s", val ? "connected" : "disconnected");
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int is_ac(const char *type)
|
||||
{
|
||||
static const char *types[] = {
|
||||
"Mains",
|
||||
"USB",
|
||||
"BrickID",
|
||||
"Wireless",
|
||||
NULL
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; types[i]; i++) {
|
||||
if (!strncmp(type, types[i], strlen(types[i])))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle power_supply change events (original keventd functionality).
|
||||
*/
|
||||
static void power_supply_change(const struct uevent *ev, char *buf, size_t len)
|
||||
{
|
||||
int ac = 0;
|
||||
size_t i, hdrlen;
|
||||
|
||||
/* Skip past header to key=value pairs */
|
||||
hdrlen = strlen(buf) + 1;
|
||||
if (ev->devpath)
|
||||
hdrlen += strlen(ev->devpath) + 1;
|
||||
|
||||
for (i = hdrlen; i < len; ) {
|
||||
char *line = buf + i;
|
||||
|
||||
if (!*line)
|
||||
break;
|
||||
|
||||
if (!strncmp(line, "POWER_SUPPLY_TYPE=", 18)) {
|
||||
ac = is_ac(&line[18]);
|
||||
} else if (!strncmp(line, "POWER_SUPPLY_ONLINE=", 20) && ac) {
|
||||
if (check_online(&line[20])) {
|
||||
if (!num_ac_online)
|
||||
cond_emit(COND_SYS, "pwr/ac", 1);
|
||||
num_ac_online++;
|
||||
} else {
|
||||
if (num_ac_online > 0)
|
||||
num_ac_online--;
|
||||
if (!num_ac_online)
|
||||
cond_emit(COND_SYS, "pwr/ac", 0);
|
||||
}
|
||||
}
|
||||
|
||||
i += strlen(line) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Queue bookkeeping for the D-Bus Settle/QueueEmpty surface: the
|
||||
* highest kernel seqnum keventd has fully handled. The baseline is
|
||||
* taken at bus init, events from before keventd started are not ours
|
||||
* to wait for.
|
||||
*/
|
||||
static unsigned long long seq_processed;
|
||||
|
||||
static void note_seqnum(const char *seqnum)
|
||||
{
|
||||
unsigned long long seq;
|
||||
|
||||
if (!seqnum)
|
||||
return;
|
||||
|
||||
seq = strtoull(seqnum, NULL, 10);
|
||||
if (seq > seq_processed)
|
||||
seq_processed = seq;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a single uevent from the kernel.
|
||||
*/
|
||||
static void handle_uevent(char *buf, size_t len)
|
||||
{
|
||||
struct uevent ev;
|
||||
|
||||
if (uevent_parse(buf, len, &ev))
|
||||
return;
|
||||
|
||||
logit(LOG_DEBUG, "uevent: %s@%s subsys=%s dev=%s major=%d minor=%d",
|
||||
uevent_action_str(ev.action), ev.devpath ?: "",
|
||||
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) {
|
||||
/* Firmware loading takes priority */
|
||||
if (ev.firmware)
|
||||
firmware_load(&ev);
|
||||
|
||||
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);
|
||||
|
||||
/* class/<subsystem>/<sysname> -- covers sysfs-only
|
||||
* devices that don't get a /dev node. */
|
||||
class_cond(&ev, 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case ACT_REMOVE:
|
||||
if (!passive) {
|
||||
class_cond(&ev, 0);
|
||||
|
||||
/* Remove symlinks first */
|
||||
symlink_del(&ev);
|
||||
|
||||
/* 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;
|
||||
|
||||
case ACT_CHANGE:
|
||||
/* Handle power supply changes */
|
||||
if (ev.subsystem && !strcmp(ev.subsystem, "power_supply"))
|
||||
power_supply_change(&ev, buf, len);
|
||||
break;
|
||||
|
||||
case ACT_BIND:
|
||||
if (!passive)
|
||||
driver_cond(&ev, 1);
|
||||
break;
|
||||
|
||||
case ACT_UNBIND:
|
||||
if (!passive)
|
||||
driver_cond(&ev, 0);
|
||||
break;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* Fully handled: only now may QueueEmpty/Settle count it */
|
||||
note_seqnum(ev.seqnum);
|
||||
#ifdef HAVE_DBUS
|
||||
devbus_notify(ev.devpath, uevent_action_str(ev.action));
|
||||
#endif
|
||||
|
||||
uevent_env_free(&ev);
|
||||
}
|
||||
|
||||
static void init_power_supply(void)
|
||||
{
|
||||
struct dirent **d = NULL;
|
||||
char path[384];
|
||||
int i, n;
|
||||
|
||||
n = scandir(_PATH_SYSFS_PWR, &d, NULL, alphasort);
|
||||
for (i = 0; i < n; i++) {
|
||||
const char *nm = d[i]->d_name;
|
||||
char buf[10];
|
||||
|
||||
snprintf(path, sizeof(path), "%s/%s/type", _PATH_SYSFS_PWR, nm);
|
||||
if (!sysfs_read_file(path, buf, sizeof(buf)) && is_ac(buf)) {
|
||||
num_ac++;
|
||||
|
||||
snprintf(path, sizeof(path), "%s/%s/online", _PATH_SYSFS_PWR, nm);
|
||||
if (!sysfs_read_file(path, buf, sizeof(buf))) {
|
||||
if (check_online(buf))
|
||||
num_ac_online++;
|
||||
}
|
||||
}
|
||||
free(d[i]);
|
||||
}
|
||||
|
||||
if (n > 0)
|
||||
free(d);
|
||||
|
||||
/* if any power_supply is online, or none can be found */
|
||||
if (num_ac == 0 || num_ac_online > 0)
|
||||
cond_emit(COND_SYS, "pwr/ac", 1);
|
||||
}
|
||||
|
||||
static void disable_uevent_helper(void)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen("/proc/sys/kernel/hotplug", "w");
|
||||
if (!fp)
|
||||
return;
|
||||
fputs("\n", fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
static void set_logging(int prio)
|
||||
{
|
||||
setlogmask(LOG_UPTO(prio));
|
||||
level = prio;
|
||||
}
|
||||
|
||||
static void sigusr1_cb(uev_t *w, void *arg, int events)
|
||||
{
|
||||
(void)w; (void)arg; (void)events;
|
||||
debug ^= 1;
|
||||
set_logging(debug ? LOG_DEBUG : LOG_NOTICE);
|
||||
}
|
||||
|
||||
static void sigterm_cb(uev_t *w, void *arg, int events)
|
||||
{
|
||||
(void)arg; (void)events;
|
||||
uev_exit(w->ctx);
|
||||
}
|
||||
|
||||
static void sighup_cb(uev_t *w, void *arg, int events)
|
||||
{
|
||||
(void)w; (void)arg; (void)events;
|
||||
kev_rules_reload();
|
||||
}
|
||||
|
||||
static void sigchld_cb(uev_t *w, void *arg, int events)
|
||||
{
|
||||
(void)w; (void)arg; (void)events;
|
||||
|
||||
/* Reap all the children. */
|
||||
while (waitpid(-1, NULL, WNOHANG) > 0)
|
||||
;
|
||||
}
|
||||
|
||||
static void uevent_cb(uev_t *w, void *arg, int events)
|
||||
{
|
||||
char *uevent_buf = arg;
|
||||
char rebc_buf[UEVENT_BUFFER_SIZE];
|
||||
int len;
|
||||
|
||||
if (events & UEV_ERROR) {
|
||||
warn("netlink watcher error, re-arming");
|
||||
uev_io_start(w);
|
||||
return;
|
||||
}
|
||||
|
||||
len = recv(w->fd, uevent_buf, UEVENT_BUFFER_SIZE - 1, MSG_DONTWAIT);
|
||||
if (len == -1) {
|
||||
switch (errno) {
|
||||
case EINTR:
|
||||
case EAGAIN:
|
||||
return;
|
||||
case ENOBUFS:
|
||||
warn("lost events, buffer overflow");
|
||||
return;
|
||||
default:
|
||||
panic("recv failed");
|
||||
}
|
||||
}
|
||||
uevent_buf[len] = 0;
|
||||
|
||||
/* Skip libudev events (start with "libudev") */
|
||||
if (!strncmp(uevent_buf, "libudev", 7))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Save raw buffer before handle_uevent() -- uevent_parse()
|
||||
* modifies the buffer in-place (splits @ and = separators).
|
||||
* Rebroadcast needs the original kernel format intact.
|
||||
*/
|
||||
if (rebc_fd != -1)
|
||||
memcpy(rebc_buf, uevent_buf, len);
|
||||
|
||||
handle_uevent(uevent_buf, len);
|
||||
|
||||
/* Rebroadcast after processing so consumers see ready state. */
|
||||
if (rebc_fd != -1)
|
||||
rebc_event(rebc_buf, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Pidfile gate used with -c: defer pidfile() until the kernel's
|
||||
* uevent_seqnum has been stable for 200ms, so that <pid/keventd>
|
||||
* means "/dev is populated, persistent symlinks are live" rather
|
||||
* than just "listening on netlink".
|
||||
*/
|
||||
unsigned long long kev_seq_processed(void)
|
||||
{
|
||||
return seq_processed;
|
||||
}
|
||||
|
||||
static void kev_seq_baseline(void)
|
||||
{
|
||||
unsigned long long seq;
|
||||
|
||||
if (!read_uevent_seqnum(&seq) && seq > seq_processed)
|
||||
seq_processed = seq;
|
||||
}
|
||||
|
||||
int kev_queue_empty(void)
|
||||
{
|
||||
unsigned long long seq;
|
||||
|
||||
if (read_uevent_seqnum(&seq))
|
||||
return 1; /* no counter, nothing to wait for */
|
||||
|
||||
return seq <= seq_processed;
|
||||
}
|
||||
|
||||
int kev_rules_reload(void)
|
||||
{
|
||||
rules_free(&rules);
|
||||
return rules_load_all(&rules, rules_dir);
|
||||
}
|
||||
|
||||
static void coldplug_pidfile_cb(uev_t *w, void *arg, int events)
|
||||
{
|
||||
struct coldplug_gate *cg = arg;
|
||||
|
||||
(void)events;
|
||||
|
||||
if (!seqnum_stable(cg, 200))
|
||||
return;
|
||||
|
||||
pidfile(NULL);
|
||||
logit(LOG_NOTICE, "keventd ready, coldplug queue drained");
|
||||
uev_timer_stop(w);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop-gap "settle" mode: poll /sys/kernel/uevent_seqnum until it has
|
||||
* been stable for `stable_ms` (default 200ms), or until `timeout_s`
|
||||
* elapses. Imperfect -- a slow probe firing after we return still
|
||||
* races -- but matches what typical `udevadm settle` users want.
|
||||
*
|
||||
* The proper fix lives in the IPC/queue work (see audit doc).
|
||||
*/
|
||||
static int cmd_settle(int timeout_s, int stable_ms)
|
||||
{
|
||||
struct coldplug_gate cg = { 0 };
|
||||
uint64_t deadline = kev_now_ms() + (uint64_t)timeout_s * 1000;
|
||||
|
||||
while (1) {
|
||||
if (seqnum_stable(&cg, stable_ms))
|
||||
return 0;
|
||||
|
||||
if (!cg.primed) {
|
||||
fprintf(stderr, "keventd: cannot read uevent_seqnum: %s\n",
|
||||
strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (kev_now_ms() >= deadline)
|
||||
return 1;
|
||||
|
||||
usleep(50000);
|
||||
}
|
||||
}
|
||||
|
||||
static int usage(int rc)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage: keventd [-dGhnpSv] [-c] [-g GROUP] [-r DIR] [-t SECONDS]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -c Run coldplug at startup\n"
|
||||
" -d Enable debug mode (foreground, verbose)\n"
|
||||
" -g GROUP Override netlink rebroadcast group (default: %d)\n"
|
||||
" -G Disable netlink rebroadcast entirely\n"
|
||||
" -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"
|
||||
" -S Settle: wait until kernel uevent queue is quiet, then exit\n"
|
||||
" -t SEC Settle timeout in seconds (default: 30, with -S)\n"
|
||||
" -v Show version\n"
|
||||
"\n", REBC_DEFAULT_NLGROUP);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unified device manager daemon.
|
||||
*
|
||||
* Started by Finit as soon as possible when base filesystem is up,
|
||||
* modules have been probed. Handles:
|
||||
* - Device node creation/removal in /dev
|
||||
* - Persistent symlinks in /dev/disk/by-*, /dev/input/by-*
|
||||
* - Module loading via MODALIAS
|
||||
* - Firmware loading via FIRMWARE
|
||||
* - Power supply conditions (sys/pwr/ac)
|
||||
* - Device conditions (dev/)
|
||||
*/
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
uev_t netlink_watcher, sigusr1_watcher, sigterm_watcher, sighup_watcher, sigchld_watcher;
|
||||
unsigned int nlgroups = REBC_DEFAULT_NLGROUP;
|
||||
static char uevent_buf[UEVENT_BUFFER_SIZE];
|
||||
static struct coldplug_gate cg;
|
||||
struct sockaddr_nl nls = { 0 };
|
||||
static uev_t coldplug_timer;
|
||||
int settle_timeout = 30;
|
||||
int do_coldplug = 0;
|
||||
int foreground = 0;
|
||||
int do_settle = 0;
|
||||
uev_ctx_t ctx;
|
||||
int nlfd;
|
||||
int c;
|
||||
|
||||
/* Device nodes are created with explicit MODE= from rules or the
|
||||
* built-in devrules table; clear umask so mknod() honors the
|
||||
* requested bits verbatim instead of masking them. */
|
||||
umask(0);
|
||||
|
||||
while ((c = getopt(argc, argv, "cdg:Ghnpr:St:v")) != -1) {
|
||||
switch (c) {
|
||||
case 'c':
|
||||
do_coldplug = 1;
|
||||
break;
|
||||
case 'd':
|
||||
debug = 1;
|
||||
foreground = 1;
|
||||
break;
|
||||
case 'g':
|
||||
nlgroups = (unsigned int)atoi(optarg);
|
||||
break;
|
||||
case 'G':
|
||||
nlgroups = 0;
|
||||
break;
|
||||
case 'h':
|
||||
return usage(0);
|
||||
case 'n':
|
||||
foreground = 1;
|
||||
break;
|
||||
case 'p':
|
||||
passive = 1;
|
||||
break;
|
||||
case 'r':
|
||||
rules_dir = optarg;
|
||||
break;
|
||||
case 'S':
|
||||
do_settle = 1;
|
||||
break;
|
||||
case 't':
|
||||
settle_timeout = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
printf("keventd v%s\n", KEVENTD_VERSION);
|
||||
return 0;
|
||||
default:
|
||||
return usage(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (do_settle) {
|
||||
#ifdef HAVE_DBUS
|
||||
/* Ask the running keventd, which tracks the queue where
|
||||
* the events flow; fall back to polling the kernel's
|
||||
* seqnum when no bus is up. */
|
||||
int rc = devbus_client_settle(settle_timeout);
|
||||
if (rc >= 0)
|
||||
return rc;
|
||||
#endif
|
||||
return cmd_settle(settle_timeout, 200);
|
||||
}
|
||||
|
||||
if (!foreground) {
|
||||
openlog("keventd", LOG_PID, LOG_DAEMON);
|
||||
set_logging(LOG_NOTICE);
|
||||
logon = 1;
|
||||
} else {
|
||||
set_logging(debug ? LOG_DEBUG : LOG_NOTICE);
|
||||
}
|
||||
|
||||
uev_init(&ctx);
|
||||
|
||||
uev_signal_init(&ctx, &sigusr1_watcher, sigusr1_cb, NULL, SIGUSR1);
|
||||
uev_signal_init(&ctx, &sigterm_watcher, sigterm_cb, NULL, SIGTERM);
|
||||
uev_signal_init(&ctx, &sighup_watcher, sighup_cb, NULL, SIGHUP);
|
||||
uev_signal_init(&ctx, &sigchld_watcher, sigchld_cb, NULL, SIGCHLD);
|
||||
|
||||
/* Initialize condition directories */
|
||||
init_power_supply();
|
||||
|
||||
/* Disable legacy kernel uevent helper; we own events via netlink.
|
||||
* Skip in passive mode -- the hotplug daemon handles this. */
|
||||
if (!passive)
|
||||
disable_uevent_helper();
|
||||
|
||||
/* Set up netlink socket for kernel uevents */
|
||||
nlfd = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT);
|
||||
if (nlfd == -1)
|
||||
panic("failed creating netlink socket");
|
||||
|
||||
nls.nl_family = AF_NETLINK;
|
||||
nls.nl_pid = 0;
|
||||
nls.nl_groups = 1; /* Kernel uevents are on group 1 only */
|
||||
if (bind(nlfd, (void *)&nls, sizeof(struct sockaddr_nl)))
|
||||
panic("bind failed");
|
||||
|
||||
/* Increase receive buffer to reduce event loss */
|
||||
{
|
||||
int rcvbuf = 1024 * 1024;
|
||||
setsockopt(nlfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
|
||||
}
|
||||
|
||||
uev_io_init(&ctx, &netlink_watcher, uevent_cb, uevent_buf, nlfd, UEV_READ);
|
||||
|
||||
/* Initialize rebroadcast socket (default on, -G to disable).
|
||||
* Skip in passive mode -- the hotplug daemon rebroadcasts. */
|
||||
if (nlgroups && !passive)
|
||||
rebc_init(nlgroups);
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
/* Bus up before coldplug so Settle covers the coldplug drain */
|
||||
kev_seq_baseline();
|
||||
devbus_init(&ctx, passive);
|
||||
#endif
|
||||
|
||||
/* Run coldplug if requested */
|
||||
if (do_coldplug)
|
||||
coldplug();
|
||||
|
||||
/* Load udev rules from standard directories (and -r extra_dir) */
|
||||
if (!passive)
|
||||
rules_load_all(&rules, rules_dir);
|
||||
|
||||
/*
|
||||
* With -c, defer pidfile until coldplug events have actually been
|
||||
* drained, so <pid/keventd> means "/dev populated", not just
|
||||
* "listening on netlink". Without -c, nothing is queued, drop
|
||||
* the pidfile immediately as before.
|
||||
*/
|
||||
if (do_coldplug) {
|
||||
uev_timer_init(&ctx, &coldplug_timer, coldplug_pidfile_cb, &cg, 100, 100);
|
||||
logit(LOG_NOTICE, "keventd v%s started, draining coldplug queue...", KEVENTD_VERSION);
|
||||
} else {
|
||||
pidfile(NULL);
|
||||
logit(LOG_NOTICE, "keventd v%s started, waiting for events...", KEVENTD_VERSION);
|
||||
}
|
||||
|
||||
uev_run(&ctx, 0);
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
devbus_exit();
|
||||
#endif
|
||||
if (rebc_fd != -1)
|
||||
close(rebc_fd);
|
||||
close(nlfd);
|
||||
rules_free(&rules);
|
||||
logit(LOG_NOTICE, "keventd shutting down");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
@@ -0,0 +1,191 @@
|
||||
/* Unified device manager for Finit - uevent handling, device nodes, symlinks
|
||||
*
|
||||
* Copyright (c) 2021-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 FINIT_KEVENTD_H_
|
||||
#define FINIT_KEVENTD_H_
|
||||
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/queue.h> /* BSD sys/queue.h API */
|
||||
#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,
|
||||
ACT_ADD,
|
||||
ACT_REMOVE,
|
||||
ACT_CHANGE,
|
||||
ACT_MOVE,
|
||||
ACT_ONLINE,
|
||||
ACT_OFFLINE,
|
||||
ACT_BIND,
|
||||
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.
|
||||
* 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;
|
||||
char *devpath; /* /devices/pci0000:00/... */
|
||||
char *subsystem; /* block, input, net, power_supply */
|
||||
char *devname; /* sda, event0, ttyUSB0 */
|
||||
char *devtype; /* disk, partition */
|
||||
int major;
|
||||
int minor;
|
||||
char *modalias; /* module alias for auto-loading */
|
||||
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 */
|
||||
struct dev_symlink {
|
||||
TAILQ_ENTRY(dev_symlink) link;
|
||||
char *devpath; /* sysfs devpath (key for removal) */
|
||||
char *linkpath; /* /dev/disk/by-id/... */
|
||||
};
|
||||
|
||||
/* Function prototypes - uevent.c */
|
||||
|
||||
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);
|
||||
|
||||
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 devnode_add (struct uevent *ev);
|
||||
int devnode_del (struct uevent *ev);
|
||||
int netdev_add (struct uevent *ev);
|
||||
|
||||
void cond_emit (const char *prefix, const char *rel, int set);
|
||||
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_del (struct uevent *ev);
|
||||
void symlink_write_db (const char *devpath, FILE *fp);
|
||||
|
||||
int firmware_load (struct uevent *ev);
|
||||
int modprobe_load (const char *modalias);
|
||||
|
||||
int coldplug (void);
|
||||
int coldplug_trigger (const char *action, const char *subsys_glob);
|
||||
int uevent_action_valid(const char *action);
|
||||
|
||||
/* keventd.c: logging and queue/rules state, for all of keventd */
|
||||
void logit (int prio, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
uint64_t kev_now_ms (void);
|
||||
unsigned long long kev_seq_processed(void);
|
||||
int kev_queue_empty (void);
|
||||
int kev_rules_reload (void);
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
#include <uev/uev.h>
|
||||
|
||||
/* dbus.c: org.finit.Device1 on /run/keventd/bus */
|
||||
int devbus_init (uev_ctx_t *ctx, int passive);
|
||||
void devbus_exit (void);
|
||||
void devbus_notify (const char *devpath, const char *action);
|
||||
int devbus_client_settle(int timeout_s);
|
||||
#endif
|
||||
|
||||
#endif /* FINIT_KEVENTD_H_ */
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
+1626
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,121 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/50-udev-default.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
# run a command on remove events
|
||||
ACTION=="remove", ENV{REMOVE_CMD}!="", RUN+="$env{REMOVE_CMD}"
|
||||
ACTION=="remove", GOTO="default_end"
|
||||
|
||||
SUBSYSTEM=="virtio-ports", KERNEL=="vport*", ATTR{name}=="?*", SYMLINK+="virtio-ports/$attr{name}"
|
||||
|
||||
# select "system RTC" or just use the first one
|
||||
SUBSYSTEM=="rtc", ATTR{hctosys}=="1", SYMLINK+="rtc"
|
||||
SUBSYSTEM=="rtc", KERNEL=="rtc0", SYMLINK+="rtc", OPTIONS+="link_priority=-100"
|
||||
|
||||
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
|
||||
ENV{MODALIAS}!="", IMPORT{builtin}="hwdb --subsystem=$env{SUBSYSTEM}"
|
||||
|
||||
ACTION!="add", GOTO="default_end"
|
||||
|
||||
SUBSYSTEM=="tty", KERNEL=="ptmx", GROUP="tty", MODE="0666"
|
||||
SUBSYSTEM=="tty", KERNEL=="tty", GROUP="tty", MODE="0666"
|
||||
SUBSYSTEM=="tty", KERNEL=="tty[0-9]*", GROUP="tty", MODE="0620"
|
||||
SUBSYSTEM=="tty", KERNEL=="sclp_line[0-9]*", GROUP="tty", MODE="0620"
|
||||
SUBSYSTEM=="tty", KERNEL=="ttysclp[0-9]*", GROUP="tty", MODE="0620"
|
||||
SUBSYSTEM=="tty", KERNEL=="3270/tty[0-9]*", GROUP="tty", MODE="0620"
|
||||
SUBSYSTEM=="vc", KERNEL=="vcs*|vcsa*", GROUP="tty"
|
||||
KERNEL=="tty[A-Z]*[0-9]|ttymxc[0-9]*|pppox[0-9]*|ircomm[0-9]*|noz[0-9]*|rfcomm[0-9]*", GROUP="dialout"
|
||||
|
||||
SUBSYSTEM=="mem", KERNEL=="mem|kmem|port", GROUP="kmem", MODE="0640"
|
||||
|
||||
SUBSYSTEM=="input", GROUP="input"
|
||||
SUBSYSTEM=="input", KERNEL=="js[0-9]*", MODE="0664"
|
||||
|
||||
SUBSYSTEM=="video4linux", GROUP="video"
|
||||
SUBSYSTEM=="graphics", GROUP="video"
|
||||
SUBSYSTEM=="drm", KERNEL!="renderD*", GROUP="video"
|
||||
SUBSYSTEM=="dvb", GROUP="video"
|
||||
SUBSYSTEM=="media", GROUP="video"
|
||||
SUBSYSTEM=="cec", GROUP="video"
|
||||
|
||||
SUBSYSTEM=="drm", KERNEL=="renderD*", GROUP="video", MODE="0666"
|
||||
SUBSYSTEM=="kfd", GROUP="video", MODE="0666"
|
||||
|
||||
SUBSYSTEM=="misc", KERNEL=="sgx_enclave", GROUP="sgx", MODE="0660"
|
||||
SUBSYSTEM=="misc", KERNEL=="sgx_vepc", GROUP="sgx", MODE="0660"
|
||||
|
||||
# When using static_node= with non-default permissions, also update
|
||||
# tmpfiles.d/static-nodes-permissions.conf.in to keep permissions synchronized.
|
||||
|
||||
SUBSYSTEM=="sound", GROUP="audio", \
|
||||
OPTIONS+="static_node=snd/seq", OPTIONS+="static_node=snd/timer"
|
||||
|
||||
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", MODE="0664"
|
||||
|
||||
SUBSYSTEM=="firewire", TEST=="units", TEST=="model", \
|
||||
IMPORT{builtin}="hwdb 'ieee1394:node:ven$attr{vendor}mo$attr{model}units$attr{units}'"
|
||||
|
||||
SUBSYSTEM=="firewire", TEST=="units", TEST!="model", \
|
||||
IMPORT{builtin}="hwdb 'ieee1394:node:ven$attr{vendor}units$attr{units}'"
|
||||
|
||||
SUBSYSTEM=="firewire", TEST=="units", ENV{IEEE1394_UNIT_FUNCTION_MIDI}=="1", GROUP="audio"
|
||||
SUBSYSTEM=="firewire", TEST=="units", ENV{IEEE1394_UNIT_FUNCTION_AUDIO}=="1", GROUP="audio"
|
||||
SUBSYSTEM=="firewire", TEST=="units", ENV{IEEE1394_UNIT_FUNCTION_VIDEO}=="1", GROUP="video"
|
||||
|
||||
KERNEL=="parport[0-9]*", GROUP="lp"
|
||||
SUBSYSTEM=="printer", KERNEL=="lp*", GROUP="lp"
|
||||
SUBSYSTEM=="ppdev", GROUP="lp"
|
||||
KERNEL=="lp[0-9]*", GROUP="lp"
|
||||
KERNEL=="irlpt[0-9]*", GROUP="lp"
|
||||
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0701??:*", GROUP="lp"
|
||||
|
||||
SUBSYSTEM=="block", GROUP="disk"
|
||||
SUBSYSTEM=="block", KERNEL=="sr[0-9]*", GROUP="cdrom"
|
||||
SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="4|5", GROUP="cdrom"
|
||||
KERNEL=="sch[0-9]*", GROUP="cdrom"
|
||||
KERNEL=="pktcdvd[0-9]*", GROUP="cdrom"
|
||||
KERNEL=="pktcdvd", GROUP="cdrom"
|
||||
|
||||
SUBSYSTEM=="scsi_generic|scsi_tape", SUBSYSTEMS=="scsi", ATTRS{type}=="1|8", GROUP="tape"
|
||||
SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="0", GROUP="disk"
|
||||
KERNEL=="qft[0-9]*|nqft[0-9]*|zqft[0-9]*|nzqft[0-9]*|rawqft[0-9]*|nrawqft[0-9]*", GROUP="disk"
|
||||
KERNEL=="loop-control", GROUP="disk", OPTIONS+="static_node=loop-control"
|
||||
KERNEL=="btrfs-control", GROUP="disk"
|
||||
KERNEL=="rawctl", GROUP="disk"
|
||||
SUBSYSTEM=="raw", KERNEL=="raw[0-9]*", GROUP="disk"
|
||||
SUBSYSTEM=="aoe", GROUP="disk", MODE="0220"
|
||||
SUBSYSTEM=="aoe", KERNEL=="err", MODE="0440"
|
||||
|
||||
KERNEL=="rfkill", MODE="0664"
|
||||
KERNEL=="tun", MODE="0666", OPTIONS+="static_node=net/tun"
|
||||
|
||||
KERNEL=="fuse", MODE="0666", OPTIONS+="static_node=fuse"
|
||||
|
||||
# The static_node is required on s390x and ppc (they are using MODULE_ALIAS)
|
||||
KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"
|
||||
|
||||
KERNEL=="vfio", MODE="0666", OPTIONS+="static_node=vfio/vfio"
|
||||
|
||||
KERNEL=="vsock", MODE="0666"
|
||||
KERNEL=="vhost-vsock", GROUP="kvm", MODE="0666", OPTIONS+="static_node=vhost-vsock"
|
||||
|
||||
KERNEL=="vhost-net", GROUP="kvm", MODE="0666", OPTIONS+="static_node=vhost-net"
|
||||
|
||||
KERNEL=="udmabuf", GROUP="kvm"
|
||||
|
||||
SUBSYSTEM=="ptp", ATTR{clock_name}=="KVM virtual PTP", SYMLINK += "ptp_kvm"
|
||||
|
||||
SUBSYSTEM=="ptp", ATTR{clock_name}=="hyperv", SYMLINK += "ptp_hyperv"
|
||||
|
||||
SUBSYSTEM!="dmi", GOTO="dmi_end"
|
||||
ENV{ID_VENDOR}="$attr{sys_vendor}"
|
||||
ENV{ID_SYSFS_ATTRIBUTE_MODEL}=="|product_name", ENV{ID_MODEL}="$attr{product_name}"
|
||||
ENV{ID_SYSFS_ATTRIBUTE_MODEL}=="product_version", ENV{ID_MODEL}="$attr{product_version}"
|
||||
# fallback to board information
|
||||
ENV{ID_VENDOR}=="", ENV{ID_VENDOR}="$attr{board_vendor}"
|
||||
ENV{ID_MODEL}=="", ENV{ID_MODEL}="$attr{board_name}"
|
||||
LABEL="dmi_end"
|
||||
|
||||
LABEL="default_end"
|
||||
@@ -0,0 +1,22 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-autosuspend.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION!="add", GOTO="autosuspend_end"
|
||||
|
||||
# I2C rules
|
||||
SUBSYSTEM=="i2c", ATTR{name}=="cyapa", \
|
||||
ATTR{power/control}="on", GOTO="autosuspend_end"
|
||||
|
||||
# Enable autosuspend if hwdb says so. Here we are relying on
|
||||
# the hwdb import done earlier based on MODALIAS.
|
||||
ENV{ID_AUTOSUSPEND}=="1", TEST=="power/control", \
|
||||
ATTR{power/control}="auto"
|
||||
|
||||
# Disable USB persist if hwdb says so.
|
||||
ENV{ID_PERSIST}=="0", TEST=="power/persist", \
|
||||
ATTR{power/persist}="0"
|
||||
|
||||
LABEL="autosuspend_end"
|
||||
@@ -0,0 +1,17 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-block.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
# enable in-kernel media-presence polling
|
||||
ACTION=="add", SUBSYSTEM=="module", KERNEL=="block", ATTR{parameters/events_dfl_poll_msecs}=="0", \
|
||||
ATTR{parameters/events_dfl_poll_msecs}="2000"
|
||||
|
||||
# forward scsi device event to corresponding block device
|
||||
ACTION=="change", SUBSYSTEM=="scsi", ENV{DEVTYPE}=="scsi_device", TEST=="block", ATTR{block/*/uevent}="change"
|
||||
|
||||
# watch metadata changes, caused by tools closing the device node which was opened for writing
|
||||
ACTION!="remove", SUBSYSTEM=="block", \
|
||||
KERNEL=="loop*|mmcblk*[0-9]|msblk*[0-9]|mspblk*[0-9]|nvme*|sd*|vd*|xvd*|bcache*|cciss*|dasd*|ubd*|ubi*|scm*|pmem*|nbd*|zd*", \
|
||||
OPTIONS+="watch"
|
||||
@@ -0,0 +1,33 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-cdrom_id.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="cdrom_end"
|
||||
SUBSYSTEM!="block", GOTO="cdrom_end"
|
||||
KERNEL!="sr[0-9]*|vdisk*|xvd*", GOTO="cdrom_end"
|
||||
ENV{DEVTYPE}!="disk", GOTO="cdrom_end"
|
||||
|
||||
# unconditionally tag device as CDROM
|
||||
KERNEL=="sr[0-9]*", ENV{ID_CDROM}="1"
|
||||
|
||||
# stop automatically any mount units bound to the device if the media eject
|
||||
# button is pressed.
|
||||
ENV{ID_CDROM}=="1", ENV{SYSTEMD_MOUNT_DEVICE_BOUND}="1"
|
||||
|
||||
# media eject button pressed
|
||||
ENV{DISK_EJECT_REQUEST}=="?*", RUN+="cdrom_id --eject-media $devnode", GOTO="cdrom_end"
|
||||
|
||||
# import device and media properties and lock tray to
|
||||
# enable the receiving of media eject button events
|
||||
IMPORT{program}="cdrom_id --lock-media $devnode"
|
||||
|
||||
# ejecting a CD does not remove the device node, so mark the systemd device
|
||||
# unit as inactive while there is no medium; this automatically cleans up of
|
||||
# stale mounts after ejecting
|
||||
ENV{DISK_MEDIA_CHANGE}=="?*", ENV{ID_CDROM_MEDIA}!="?*", ENV{SYSTEMD_READY}="0"
|
||||
|
||||
KERNEL=="sr0", SYMLINK+="cdrom", OPTIONS+="link_priority=-100"
|
||||
|
||||
LABEL="cdrom_end"
|
||||
@@ -0,0 +1,12 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-drm.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION!="remove", SUBSYSTEM=="drm", SUBSYSTEMS=="pci|usb|platform", IMPORT{builtin}="path_id"
|
||||
|
||||
# by-path
|
||||
ENV{ID_PATH}=="?*", KERNEL=="card*", SYMLINK+="dri/by-path/$env{ID_PATH}-card"
|
||||
ENV{ID_PATH}=="?*", KERNEL=="controlD*", SYMLINK+="dri/by-path/$env{ID_PATH}-control"
|
||||
ENV{ID_PATH}=="?*", KERNEL=="renderD*", SYMLINK+="dri/by-path/$env{ID_PATH}-render"
|
||||
@@ -0,0 +1,29 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-evdev.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="evdev_end"
|
||||
KERNEL!="event*", GOTO="evdev_end"
|
||||
|
||||
# skip later rules when we find something for this input device
|
||||
IMPORT{builtin}="hwdb --subsystem=input --lookup-prefix=evdev:", \
|
||||
RUN{builtin}+="keyboard", GOTO="evdev_end"
|
||||
|
||||
# AT keyboard matching by the machine's DMI data
|
||||
DRIVERS=="atkbd", \
|
||||
IMPORT{builtin}="hwdb 'evdev:atkbd:$attr{[dmi/id]modalias}'", \
|
||||
RUN{builtin}+="keyboard", GOTO="evdev_end"
|
||||
|
||||
# device matching the input device name + properties + the machine's DMI data
|
||||
KERNELS=="input*", \
|
||||
IMPORT{builtin}="hwdb 'evdev:name:$attr{name}:phys:$attr{phys}:ev:$attr{capabilities/ev}:$attr{[dmi/id]modalias}'", \
|
||||
RUN{builtin}+="keyboard", GOTO="evdev_end"
|
||||
|
||||
# device matching the input device name and the machine's DMI data
|
||||
KERNELS=="input*", \
|
||||
IMPORT{builtin}="hwdb 'evdev:name:$attr{name}:$attr{[dmi/id]modalias}'", \
|
||||
RUN{builtin}+="keyboard", GOTO="evdev_end"
|
||||
|
||||
LABEL="evdev_end"
|
||||
@@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-fido-id.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="fido_id_end"
|
||||
|
||||
SUBSYSTEM=="hidraw", IMPORT{program}="fido_id"
|
||||
|
||||
# Tag any form of security token as such
|
||||
ENV{ID_SECURITY_TOKEN}=="1", TAG+="security-device"
|
||||
|
||||
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0b????:*", ENV{ID_SMARTCARD_READER}="1"
|
||||
# Tag any CCID device (i.e. Smartcard Reader) as security token
|
||||
ENV{ID_SMARTCARD_READER}=="1", TAG+="security-device"
|
||||
|
||||
LABEL="fido_id_end"
|
||||
@@ -0,0 +1,12 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-input-id.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="id_input_end"
|
||||
|
||||
SUBSYSTEM=="input", ENV{ID_INPUT}=="", IMPORT{builtin}="input_id"
|
||||
SUBSYSTEM=="input", IMPORT{builtin}="hwdb --subsystem=input --lookup-prefix=id-input:modalias:"
|
||||
|
||||
LABEL="id_input_end"
|
||||
@@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-persistent-alsa.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="persistent_alsa_end"
|
||||
SUBSYSTEM!="sound", GOTO="persistent_alsa_end"
|
||||
KERNEL!="controlC[0-9]*", GOTO="persistent_alsa_end"
|
||||
|
||||
SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{builtin}="usb_id"
|
||||
ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="?*", SYMLINK+="snd/by-id/$env{ID_BUS}-$env{ID_SERIAL}-$env{ID_USB_INTERFACE_NUM}"
|
||||
ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="", SYMLINK+="snd/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
|
||||
|
||||
IMPORT{builtin}="path_id"
|
||||
ENV{ID_PATH}=="?*", SYMLINK+="snd/by-path/$env{ID_PATH}"
|
||||
|
||||
LABEL="persistent_alsa_end"
|
||||
@@ -0,0 +1,46 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-persistent-input.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="persistent_input_end"
|
||||
SUBSYSTEM!="input", GOTO="persistent_input_end"
|
||||
SUBSYSTEMS=="bluetooth", ENV{ID_BUS}="bluetooth", GOTO="persistent_input_end"
|
||||
# Bluetooth devices don't always have the bluetooth subsystem
|
||||
ATTRS{id/bustype}=="0005", ENV{ID_BUS}="bluetooth", GOTO="persistent_input_end"
|
||||
SUBSYSTEMS=="rmi4", ENV{ID_BUS}="rmi"
|
||||
SUBSYSTEMS=="serio", ENV{ID_BUS}="i8042"
|
||||
|
||||
SUBSYSTEMS=="usb", ENV{ID_BUS}=="", IMPORT{builtin}="usb_id"
|
||||
|
||||
# determine class name for persistent symlinks
|
||||
ENV{ID_INPUT_KEYBOARD}=="?*", ENV{.INPUT_CLASS}="kbd"
|
||||
ENV{ID_INPUT_MOUSE}=="?*", ENV{.INPUT_CLASS}="mouse"
|
||||
ENV{ID_INPUT_TOUCHPAD}=="?*", ENV{.INPUT_CLASS}="mouse"
|
||||
ENV{ID_INPUT_TABLET}=="?*", ENV{.INPUT_CLASS}="mouse"
|
||||
ENV{ID_INPUT_JOYSTICK}=="?*", ENV{.INPUT_CLASS}="joystick"
|
||||
DRIVERS=="pcspkr", ENV{.INPUT_CLASS}="spkr"
|
||||
ATTRS{name}=="*dvb*|*DVB*|* IR *", ENV{.INPUT_CLASS}="ir"
|
||||
|
||||
# fill empty serial number
|
||||
ENV{.INPUT_CLASS}=="?*", ENV{ID_SERIAL}=="", ENV{ID_SERIAL}="noserial"
|
||||
|
||||
# by-id links
|
||||
KERNEL=="mouse*|js*", ENV{ID_BUS}=="?*", ENV{.INPUT_CLASS}=="?*", ATTRS{bInterfaceNumber}=="|00", SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-$env{.INPUT_CLASS}"
|
||||
KERNEL=="mouse*|js*", ENV{ID_BUS}=="?*", ENV{.INPUT_CLASS}=="?*", ATTRS{bInterfaceNumber}=="?*", ATTRS{bInterfaceNumber}!="00", SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$attr{bInterfaceNumber}-$env{.INPUT_CLASS}"
|
||||
KERNEL=="event*", ENV{ID_BUS}=="?*", ENV{.INPUT_CLASS}=="?*", ATTRS{bInterfaceNumber}=="|00", SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-event-$env{.INPUT_CLASS}"
|
||||
KERNEL=="event*", ENV{ID_BUS}=="?*", ENV{.INPUT_CLASS}=="?*", ATTRS{bInterfaceNumber}=="?*", ATTRS{bInterfaceNumber}!="00", SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$attr{bInterfaceNumber}-event-$env{.INPUT_CLASS}"
|
||||
# allow empty class for USB devices, by appending the interface number
|
||||
SUBSYSTEMS=="usb", ENV{ID_BUS}=="?*", KERNEL=="event*", ENV{.INPUT_CLASS}=="", ATTRS{bInterfaceNumber}=="?*", \
|
||||
SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-event-if$attr{bInterfaceNumber}"
|
||||
|
||||
# by-path
|
||||
SUBSYSTEMS=="pci|usb|platform|acpi", IMPORT{builtin}="path_id"
|
||||
ENV{ID_PATH}=="?*", KERNEL=="mouse*|js*", ENV{.INPUT_CLASS}=="?*", SYMLINK+="input/by-path/$env{ID_PATH}-$env{.INPUT_CLASS}"
|
||||
ENV{ID_PATH}=="?*", KERNEL=="event*", ENV{.INPUT_CLASS}=="?*", SYMLINK+="input/by-path/$env{ID_PATH}-event-$env{.INPUT_CLASS}"
|
||||
# allow empty class for platform, usb and i2c devices; platform supports only a single interface that way
|
||||
SUBSYSTEMS=="usb|platform|i2c", ENV{ID_PATH}=="?*", KERNEL=="event*", ENV{.INPUT_CLASS}=="", \
|
||||
SYMLINK+="input/by-path/$env{ID_PATH}-event"
|
||||
|
||||
LABEL="persistent_input_end"
|
||||
@@ -0,0 +1,40 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-persistent-storage-tape.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
# persistent storage links: /dev/tape/{by-id,by-path}
|
||||
|
||||
ACTION=="remove", GOTO="persistent_storage_tape_end"
|
||||
ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_tape_end"
|
||||
|
||||
# type 8 devices are "Medium Changers"
|
||||
SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \
|
||||
SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL} tape/by-id/scsi-$env{ID_SERIAL}-changer"
|
||||
|
||||
# iSCSI devices from the same host have all the same ID_SERIAL,
|
||||
# but additionally a property named ID_SCSI_SERIAL.
|
||||
SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", ENV{ID_SCSI_SERIAL}=="?*", \
|
||||
SYMLINK+="tape/by-id/scsi-$env{ID_SCSI_SERIAL}"
|
||||
|
||||
SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{builtin}="path_id", \
|
||||
SYMLINK+="tape/by-path/$env{ID_PATH}-changer"
|
||||
|
||||
SUBSYSTEM!="scsi_tape", GOTO="persistent_storage_tape_end"
|
||||
|
||||
KERNEL=="st*[0-9]|nst*[0-9]", ATTRS{ieee1394_id}=="?*", ENV{ID_SERIAL}="$attr{ieee1394_id}", ENV{ID_BUS}="ieee1394"
|
||||
KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
|
||||
KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", KERNELS=="[0-9]*:*[0-9]", ENV{.BSG_DEV}="$root/bsg/$id"
|
||||
KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --whitelisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
|
||||
KERNEL=="st*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}", OPTIONS+="link_priority=10"
|
||||
KERNEL=="st*[0-9]", ENV{ID_SCSI_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SCSI_SERIAL}"
|
||||
KERNEL=="nst*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}-nst"
|
||||
KERNEL=="nst*[0-9]", ENV{ID_SCSI_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SCSI_SERIAL}-nst"
|
||||
|
||||
# by-path (parent device path)
|
||||
KERNEL=="st*[0-9]|nst*[0-9]", IMPORT{builtin}="path_id"
|
||||
KERNEL=="st*[0-9]", ENV{ID_PATH}=="?*", SYMLINK+="tape/by-path/$env{ID_PATH}"
|
||||
KERNEL=="nst*[0-9]", ENV{ID_PATH}=="?*", SYMLINK+="tape/by-path/$env{ID_PATH}-nst"
|
||||
|
||||
LABEL="persistent_storage_tape_end"
|
||||
@@ -0,0 +1,133 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-persistent-storage.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
# persistent storage links: /dev/disk/{by-id,by-uuid,by-label,by-path}
|
||||
# scheme based on "Linux persistent device names", 2004, Hannes Reinecke <hare@suse.de>
|
||||
|
||||
ACTION=="remove", GOTO="persistent_storage_end"
|
||||
ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_end"
|
||||
|
||||
SUBSYSTEM!="block|ubi", GOTO="persistent_storage_end"
|
||||
KERNEL!="loop*|mmcblk*[0-9]|msblk*[0-9]|mspblk*[0-9]|nvme*|sd*|sr*|vd*|xvd*|bcache*|cciss*|dasd*|ubd*|ubi*|scm*|pmem*|nbd*|zd*", GOTO="persistent_storage_end"
|
||||
|
||||
# ignore partitions that span the entire disk
|
||||
TEST=="whole_disk", GOTO="persistent_storage_end"
|
||||
|
||||
# For partitions import parent disk ID_* information, except ID_FS_*.
|
||||
#
|
||||
# This is particularly important on media where a filesystem superblock and
|
||||
# partition table are found on the same level, e.g. common Linux distro ISO
|
||||
# installation media.
|
||||
#
|
||||
# In the case where a partition device points to the same filesystem that
|
||||
# was detected on the parent disk, the ID_FS_* information is already
|
||||
# present on the partition devices as well as the parent, so no need to
|
||||
# propagate it. In the case where the partition device points to a different
|
||||
# filesystem, merging the parent ID_FS_ properties would lead to
|
||||
# inconsistencies, so we avoid doing so.
|
||||
ENV{DEVTYPE}=="partition", \
|
||||
IMPORT{parent}="ID_[!F]*", IMPORT{parent}="ID_", \
|
||||
IMPORT{parent}="ID_F[!S]*", IMPORT{parent}="ID_F", \
|
||||
IMPORT{parent}="ID_FS[!_]*", IMPORT{parent}="ID_FS"
|
||||
|
||||
# NVMe
|
||||
KERNEL=="nvme*[0-9]n*[0-9]", ATTR{wwid}=="?*", SYMLINK+="disk/by-id/nvme-$attr{wwid}"
|
||||
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{wwid}=="?*", SYMLINK+="disk/by-id/nvme-$attr{wwid}-part%n"
|
||||
|
||||
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{serial}=="?*", ENV{ID_SERIAL_SHORT}="$attr{serial}"
|
||||
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{wwid}=="?*", ENV{ID_WWN}="$attr{wwid}"
|
||||
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{model}=="?*", ENV{ID_MODEL}="$attr{model}"
|
||||
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{firmware_rev}=="?*", ENV{ID_REVISION}="$attr{firmware_rev}"
|
||||
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
|
||||
OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
|
||||
|
||||
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{serial}=="?*", ENV{ID_SERIAL_SHORT}="$attr{serial}"
|
||||
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{model}=="?*", ENV{ID_MODEL}="$attr{model}"
|
||||
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{firmware_rev}=="?*", ENV{ID_REVISION}="$attr{firmware_rev}"
|
||||
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
|
||||
OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
|
||||
|
||||
# virtio-blk
|
||||
KERNEL=="vd*[!0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}"
|
||||
KERNEL=="vd*[0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}-part%n"
|
||||
|
||||
# ATA
|
||||
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", IMPORT{program}="ata_id --export $devnode"
|
||||
|
||||
# ATAPI devices (SPC-3 or later)
|
||||
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{type}=="5", ATTRS{scsi_level}=="[6-9]*", IMPORT{program}="ata_id --export $devnode"
|
||||
|
||||
# Run ata_id on non-removable USB Mass Storage (SATA/PATA disks in enclosures)
|
||||
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", ATTR{removable}=="0", SUBSYSTEMS=="usb", IMPORT{program}="ata_id --export $devnode"
|
||||
|
||||
# Fall back usb_id for USB devices
|
||||
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
|
||||
|
||||
# SCSI devices
|
||||
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="scsi"
|
||||
KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="cciss"
|
||||
KERNEL=="sd*|sr*|cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
|
||||
KERNEL=="sd*|cciss*", ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}-part%n"
|
||||
|
||||
# PMEM devices
|
||||
KERNEL=="pmem*", ENV{DEVTYPE}=="disk", ATTRS{uuid}=="?*", SYMLINK+="disk/by-id/pmem-$attr{uuid}"
|
||||
|
||||
# FireWire
|
||||
KERNEL=="sd*[!0-9]|sr*", ATTRS{ieee1394_id}=="?*", SYMLINK+="disk/by-id/ieee1394-$attr{ieee1394_id}"
|
||||
KERNEL=="sd*[0-9]", ATTRS{ieee1394_id}=="?*", SYMLINK+="disk/by-id/ieee1394-$attr{ieee1394_id}-part%n"
|
||||
|
||||
# MMC
|
||||
KERNEL=="mmcblk[0-9]", SUBSYSTEMS=="mmc", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}"
|
||||
KERNEL=="mmcblk[0-9]", SUBSYSTEMS=="mmc", ATTRS{name}=="?*", ENV{ID_NAME}="$attr{name}"
|
||||
KERNEL=="mmcblk[0-9]", ENV{ID_NAME}=="?*", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}"
|
||||
KERNEL=="mmcblk[0-9]p[0-9]*", ENV{ID_NAME}=="?*", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}-part%n"
|
||||
|
||||
# Memstick
|
||||
KERNEL=="msblk[0-9]|mspblk[0-9]", SUBSYSTEMS=="memstick", ATTRS{name}=="?*", ATTRS{serial}=="?*", \
|
||||
ENV{ID_NAME}="$attr{name}", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/memstick-$env{ID_NAME}_$env{ID_SERIAL}"
|
||||
KERNEL=="msblk[0-9]p[0-9]|mspblk[0-9]p[0-9]", ENV{ID_NAME}=="?*", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/memstick-$env{ID_NAME}_$env{ID_SERIAL}-part%n"
|
||||
|
||||
# by-path
|
||||
ENV{DEVTYPE}=="disk", DEVPATH!="*/virtual/*", IMPORT{builtin}="path_id"
|
||||
KERNEL=="mmcblk[0-9]boot[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}-boot%n"
|
||||
KERNEL!="mmcblk[0-9]boot[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}"
|
||||
ENV{DEVTYPE}=="partition", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}-part%n"
|
||||
# compatible links for ATA devices
|
||||
KERNEL!="mmcblk[0-9]boot[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_PATH_ATA_COMPAT}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH_ATA_COMPAT}"
|
||||
ENV{DEVTYPE}=="partition", ENV{ID_PATH_ATA_COMPAT}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH_ATA_COMPAT}-part%n"
|
||||
|
||||
# legacy virtio-pci by-path links (deprecated)
|
||||
KERNEL=="vd*[!0-9]", ENV{ID_PATH}=="pci-*", SYMLINK+="disk/by-path/virtio-$env{ID_PATH}"
|
||||
KERNEL=="vd*[0-9]", ENV{ID_PATH}=="pci-*", SYMLINK+="disk/by-path/virtio-$env{ID_PATH}-part%n"
|
||||
|
||||
# probe filesystem metadata of optical drives which have a media inserted
|
||||
KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DATA}=="?*", ENV{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}=="?*", \
|
||||
IMPORT{builtin}="blkid --hint=session_offset=$env{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}"
|
||||
# single-session CDs do not have ID_CDROM_MEDIA_SESSION_LAST_OFFSET
|
||||
KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DATA}=="?*", ENV{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}=="", \
|
||||
IMPORT{builtin}="blkid --noraid"
|
||||
|
||||
# probe filesystem metadata of disks
|
||||
KERNEL!="sr*|mmcblk[0-9]boot[0-9]", IMPORT{builtin}="blkid"
|
||||
|
||||
# by-label/by-uuid links (filesystem metadata)
|
||||
ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_UUID_ENC}=="?*", SYMLINK+="disk/by-uuid/$env{ID_FS_UUID_ENC}"
|
||||
ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_LABEL_ENC}=="?*", SYMLINK+="disk/by-label/$env{ID_FS_LABEL_ENC}"
|
||||
|
||||
# by-id (World Wide Name)
|
||||
ENV{DEVTYPE}=="disk", ENV{ID_WWN_WITH_EXTENSION}=="?*", SYMLINK+="disk/by-id/wwn-$env{ID_WWN_WITH_EXTENSION}"
|
||||
ENV{DEVTYPE}=="partition", ENV{ID_WWN_WITH_EXTENSION}=="?*", SYMLINK+="disk/by-id/wwn-$env{ID_WWN_WITH_EXTENSION}-part%n"
|
||||
|
||||
# by-partlabel/by-partuuid links (partition metadata)
|
||||
ENV{ID_PART_ENTRY_UUID}=="?*", SYMLINK+="disk/by-partuuid/$env{ID_PART_ENTRY_UUID}"
|
||||
ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_NAME}=="?*", SYMLINK+="disk/by-partlabel/$env{ID_PART_ENTRY_NAME}"
|
||||
|
||||
# by-diskseq link (if an app is told to open a path like this, they may parse
|
||||
# the diskseq number from the path, then issue BLKGETDISKSEQ to verify they really got
|
||||
# the right device, to access specific disks in a race-free fashion)
|
||||
ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"
|
||||
|
||||
LABEL="persistent_storage_end"
|
||||
@@ -0,0 +1,24 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-persistent-v4l.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="persistent_v4l_end"
|
||||
SUBSYSTEM!="video4linux", GOTO="persistent_v4l_end"
|
||||
ENV{MAJOR}=="", GOTO="persistent_v4l_end"
|
||||
|
||||
IMPORT{program}="v4l_id $devnode"
|
||||
|
||||
SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
|
||||
KERNEL=="video*", ENV{ID_SERIAL}=="?*", SYMLINK+="v4l/by-id/$env{ID_BUS}-$env{ID_SERIAL}-video-index$attr{index}"
|
||||
|
||||
# check for valid "index" number
|
||||
TEST!="index", GOTO="persistent_v4l_end"
|
||||
ATTR{index}!="?*", GOTO="persistent_v4l_end"
|
||||
|
||||
IMPORT{builtin}="path_id"
|
||||
ENV{ID_PATH}=="?*", KERNEL=="video*|vbi*", SYMLINK+="v4l/by-path/$env{ID_PATH}-video-index$attr{index}"
|
||||
ENV{ID_PATH}=="?*", KERNEL=="audio*", SYMLINK+="v4l/by-path/$env{ID_PATH}-audio-index$attr{index}"
|
||||
|
||||
LABEL="persistent_v4l_end"
|
||||
@@ -0,0 +1,38 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-sensor.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="sensor_end"
|
||||
|
||||
# device matching the sensor's label, name and the machine's DMI data for IIO devices
|
||||
SUBSYSTEM=="iio", KERNEL=="iio*", SUBSYSTEMS=="usb|i2c|platform", ATTR{label}!="", \
|
||||
IMPORT{builtin}="hwdb 'sensor:$attr{label}:modalias:$attr{modalias}:$attr{[dmi/id]modalias}'", \
|
||||
GOTO="sensor_end"
|
||||
|
||||
# Before Linux v6.0, cros-ec-accel used a non-standard 'location' sysfs file
|
||||
SUBSYSTEM=="iio", KERNEL=="iio*", SUBSYSTEMS=="platform", \
|
||||
ATTR{name}=="cros-ec-accel|cros-ec-accel-legacy", ATTR{location}=="base", \
|
||||
IMPORT{builtin}="hwdb 'sensor:accel-base:modalias:$attr{modalias}:$attr{[dmi/id]modalias}'", \
|
||||
GOTO="sensor_end"
|
||||
|
||||
SUBSYSTEM=="iio", KERNEL=="iio*", SUBSYSTEMS=="platform", \
|
||||
ATTR{name}=="cros-ec-accel|cros-ec-accel-legacy", ATTR{location}=="lid", \
|
||||
IMPORT{builtin}="hwdb 'sensor:accel-display:modalias:$attr{modalias}:$attr{[dmi/id]modalias}'", \
|
||||
GOTO="sensor_end"
|
||||
|
||||
# device matching the sensor's name and the machine's DMI data for IIO devices
|
||||
SUBSYSTEM=="iio", KERNEL=="iio*", SUBSYSTEMS=="usb|i2c|platform", \
|
||||
IMPORT{builtin}="hwdb 'sensor:modalias:$attr{modalias}:$attr{[dmi/id]modalias}'", \
|
||||
GOTO="sensor_end"
|
||||
|
||||
SUBSYSTEM=="input", ENV{ID_INPUT_ACCELEROMETER}=="1", SUBSYSTEMS=="acpi", \
|
||||
IMPORT{builtin}="hwdb 'sensor:modalias:acpi:$attr{hid}:$attr{[dmi/id]modalias}'", \
|
||||
GOTO="sensor_end"
|
||||
|
||||
SUBSYSTEM=="input", ENV{ID_INPUT_ACCELEROMETER}=="1", SUBSYSTEMS=="platform", \
|
||||
IMPORT{builtin}="hwdb 'sensor:modalias:platform:$id:$attr{[dmi/id]modalias}'", \
|
||||
GOTO="sensor_end"
|
||||
|
||||
LABEL="sensor_end"
|
||||
@@ -0,0 +1,30 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/60-serial.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="serial_end"
|
||||
SUBSYSTEM!="tty", GOTO="serial_end"
|
||||
|
||||
SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
|
||||
SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci"
|
||||
SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
|
||||
|
||||
# /dev/serial/by-path/, /dev/serial/by-id/ for USB devices
|
||||
KERNEL!="ttyUSB[0-9]*|ttyACM[0-9]*", GOTO="serial_end"
|
||||
|
||||
SUBSYSTEMS=="usb-serial", ENV{.ID_PORT}="$attr{port_number}"
|
||||
|
||||
IMPORT{builtin}="path_id"
|
||||
ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", SYMLINK+="serial/by-path/$env{ID_PATH}"
|
||||
ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", SYMLINK+="serial/by-path/$env{ID_PATH}-port$env{.ID_PORT}"
|
||||
|
||||
IMPORT{builtin}="usb_id"
|
||||
ENV{ID_SERIAL}=="", GOTO="serial_end"
|
||||
SUBSYSTEMS=="usb", ENV{ID_USB_INTERFACE_NUM}="$attr{bInterfaceNumber}"
|
||||
ENV{ID_USB_INTERFACE_NUM}=="", GOTO="serial_end"
|
||||
ENV{.ID_PORT}=="", SYMLINK+="serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}"
|
||||
ENV{.ID_PORT}=="?*", SYMLINK+="serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}-port$env{.ID_PORT}"
|
||||
|
||||
LABEL="serial_end"
|
||||
@@ -0,0 +1,21 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/64-btrfs.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
SUBSYSTEM!="block", GOTO="btrfs_end"
|
||||
ACTION=="remove", GOTO="btrfs_end"
|
||||
ENV{ID_FS_TYPE}!="btrfs", GOTO="btrfs_end"
|
||||
ENV{SYSTEMD_READY}=="0", GOTO="btrfs_end"
|
||||
|
||||
# let the kernel know about this btrfs filesystem, and check if it is complete
|
||||
IMPORT{builtin}="btrfs ready $devnode"
|
||||
|
||||
# mark the device as not ready to be used by the system
|
||||
ENV{ID_BTRFS_READY}=="0", ENV{SYSTEMD_READY}="0"
|
||||
|
||||
# reconsider pending devices in case when multidevice volume awaits
|
||||
ENV{ID_BTRFS_READY}=="1", RUN+="/usr/bin/udevadm trigger -s block -p ID_BTRFS_READY=0"
|
||||
|
||||
LABEL="btrfs_end"
|
||||
@@ -0,0 +1,13 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/70-camera.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="camera_end"
|
||||
|
||||
SUBSYSTEM=="video4linux", ENV{ID_BUS}="usb" , \
|
||||
IMPORT{builtin}="hwdb 'camera:usb:v$env{ID_VENDOR_ID}p$env{ID_MODEL_ID}:name:$attr{name}:'", \
|
||||
GOTO="camera_end"
|
||||
|
||||
LABEL="camera_end"
|
||||
@@ -0,0 +1,16 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/70-joystick.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="joystick_end"
|
||||
ENV{ID_INPUT_JOYSTICK}=="", GOTO="joystick_end"
|
||||
KERNEL!="event*", GOTO="joystick_end"
|
||||
|
||||
# joystick:<bustype>:v<vid>p<pid>:name:<name>:*
|
||||
KERNELS=="input*", ENV{ID_BUS}!="", \
|
||||
IMPORT{builtin}="hwdb 'joystick:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
|
||||
GOTO="joystick_end"
|
||||
|
||||
LABEL="joystick_end"
|
||||
@@ -0,0 +1,12 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/70-memory.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="memory_end"
|
||||
SUBSYSTEM!="dmi", GOTO="memory_end"
|
||||
|
||||
IMPORT{program}="dmi_memory_id"
|
||||
|
||||
LABEL="memory_end"
|
||||
@@ -0,0 +1,22 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/70-mouse.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="mouse_end"
|
||||
KERNEL!="event*", GOTO="mouse_end"
|
||||
ENV{ID_INPUT_MOUSE}=="", GOTO="mouse_end"
|
||||
|
||||
# mouse:<subsystem>:v<vid>p<pid>:name:<name>:*
|
||||
KERNELS=="input*", ENV{ID_BUS}=="usb", \
|
||||
IMPORT{builtin}="hwdb 'mouse:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
|
||||
GOTO="mouse_end"
|
||||
KERNELS=="input*", ENV{ID_BUS}=="bluetooth", \
|
||||
IMPORT{builtin}="hwdb 'mouse:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
|
||||
GOTO="mouse_end"
|
||||
DRIVERS=="psmouse", SUBSYSTEMS=="serio", \
|
||||
IMPORT{builtin}="hwdb 'mouse:ps2::name:$attr{device/name}:'", \
|
||||
GOTO="mouse_end"
|
||||
|
||||
LABEL="mouse_end"
|
||||
@@ -0,0 +1,17 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/70-touchpad.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="touchpad_end"
|
||||
ENV{ID_INPUT}=="", GOTO="touchpad_end"
|
||||
ENV{ID_INPUT_TOUCHPAD}=="", GOTO="touchpad_end"
|
||||
KERNEL!="event*", GOTO="touchpad_end"
|
||||
|
||||
# touchpad:<subsystem>:v<vid>p<pid>:name:<name>:*
|
||||
KERNELS=="input*", ENV{ID_BUS}!="", \
|
||||
IMPORT{builtin}="hwdb 'touchpad:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
|
||||
GOTO="touchpad_end"
|
||||
|
||||
LABEL="touchpad_end"
|
||||
@@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/75-net-description.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="net_end"
|
||||
SUBSYSTEM!="net", GOTO="net_end"
|
||||
|
||||
IMPORT{builtin}="net_id"
|
||||
|
||||
SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
|
||||
SUBSYSTEMS=="usb", GOTO="net_end"
|
||||
|
||||
SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
|
||||
SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci"
|
||||
|
||||
LABEL="net_end"
|
||||
@@ -0,0 +1,11 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/75-probe_mtd.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION!="add", GOTO="mtd_probe_end"
|
||||
|
||||
KERNEL=="mtd*ro", IMPORT{program}="mtd_probe $devnode"
|
||||
|
||||
LABEL="mtd_probe_end"
|
||||
@@ -0,0 +1,100 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/78-sound-card.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
SUBSYSTEM!="sound", GOTO="sound_end"
|
||||
|
||||
ACTION=="add|change", KERNEL=="controlC*", ATTR{../uevent}="change"
|
||||
ACTION!="change", GOTO="sound_end"
|
||||
|
||||
# Ok, we probably need a little explanation here for what the two lines above
|
||||
# are good for.
|
||||
#
|
||||
# The story goes like this: when ALSA registers a new sound card it emits a
|
||||
# series of 'add' events to userspace, for the main card device and for all the
|
||||
# child device nodes that belong to it. udev relays those to applications,
|
||||
# however only maintains the order between father and child, but not between
|
||||
# the siblings. The control device node creation can be used as synchronization
|
||||
# point. All other devices that belong to a card are created in the kernel
|
||||
# before it. However unfortunately due to the fact that siblings are forwarded
|
||||
# out of order by udev this fact is lost to applications.
|
||||
#
|
||||
# OTOH before an application can open a device it needs to make sure that all
|
||||
# its device nodes are completely created and set up.
|
||||
#
|
||||
# As a workaround for this issue we have added the udev rule above which will
|
||||
# generate a 'change' event on the main card device from the 'add' event of the
|
||||
# card's control device. Due to the ordering semantics of udev this event will
|
||||
# only be relayed after all child devices have finished processing properly.
|
||||
# When an application needs to listen for appearing devices it can hence look
|
||||
# for 'change' events only, and ignore the actual 'add' events.
|
||||
#
|
||||
# When the application is initialized at the same time as a device is plugged
|
||||
# in it may need to figure out if the 'change' event has already been triggered
|
||||
# or not for a card. To find that out we store the flag environment variable
|
||||
# SOUND_INITIALIZED on the device which simply tells us if the card 'change'
|
||||
# event has already been processed.
|
||||
|
||||
KERNEL!="card*", GOTO="sound_end"
|
||||
|
||||
ENV{SOUND_INITIALIZED}="1"
|
||||
|
||||
IMPORT{builtin}="hwdb"
|
||||
SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
|
||||
SUBSYSTEMS=="usb", GOTO="skip_pci"
|
||||
|
||||
SUBSYSTEMS=="firewire", ATTRS{guid}=="?*", \
|
||||
ENV{ID_BUS}="firewire", ENV{ID_SERIAL}="$attr{guid}", ENV{ID_SERIAL_SHORT}="$attr{guid}", \
|
||||
ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{model}", \
|
||||
ENV{ID_VENDOR}="$attr{vendor_name}", ENV{ID_MODEL}="$attr{model_name}"
|
||||
SUBSYSTEMS=="firewire", GOTO="skip_pci"
|
||||
|
||||
SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
|
||||
SUBSYSTEMS=="pci", GOTO="skip_pci"
|
||||
|
||||
# If we reach here, the device nor any of its parents are USB/PCI/firewire bus devices.
|
||||
# If we now find a parent that is a platform device, assume that we're working with
|
||||
# an internal sound card.
|
||||
SUBSYSTEMS=="platform", ENV{SOUND_FORM_FACTOR}="internal", GOTO="sound_end"
|
||||
|
||||
LABEL="skip_pci"
|
||||
|
||||
# Define ID_ID if ID_BUS and ID_SERIAL are set. This will work for both
|
||||
# USB and firewire.
|
||||
ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="?*", ENV{ID_ID}="$env{ID_BUS}-$env{ID_SERIAL}-$env{ID_USB_INTERFACE_NUM}"
|
||||
ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="", ENV{ID_ID}="$env{ID_BUS}-$env{ID_SERIAL}"
|
||||
|
||||
IMPORT{builtin}="path_id"
|
||||
|
||||
# The values used here for $SOUND_FORM_FACTOR and $SOUND_CLASS should be kept
|
||||
# in sync with those defined for PulseAudio's src/pulse/proplist.h
|
||||
# PA_PROP_DEVICE_FORM_FACTOR, PA_PROP_DEVICE_CLASS properties.
|
||||
|
||||
# If the first PCM device of this card has the pcm class 'modem', then the card is a modem
|
||||
ATTR{pcmC%nD0p/pcm_class}=="modem", ENV{SOUND_CLASS}="modem", GOTO="sound_end"
|
||||
|
||||
# Identify cards on the internal PCI bus as internal
|
||||
SUBSYSTEMS=="pci", DEVPATH=="*/0000:00:??.?/sound/*", ENV{SOUND_FORM_FACTOR}="internal", GOTO="sound_end"
|
||||
|
||||
# Devices that also support Image/Video interfaces are most likely webcams
|
||||
SUBSYSTEMS=="usb", ENV{ID_USB_INTERFACES}=="*:0e????:*", ENV{SOUND_FORM_FACTOR}="webcam", GOTO="sound_end"
|
||||
|
||||
# Matching on the model strings is a bit ugly, I admit
|
||||
ENV{ID_MODEL}=="*[Ss]peaker*", ENV{SOUND_FORM_FACTOR}="speaker", GOTO="sound_end"
|
||||
ENV{ID_MODEL_FROM_DATABASE}=="*[Ss]peaker*", ENV{SOUND_FORM_FACTOR}="speaker", GOTO="sound_end"
|
||||
|
||||
ENV{ID_MODEL}=="*[Hh]eadphone*", ENV{SOUND_FORM_FACTOR}="headphone", GOTO="sound_end"
|
||||
ENV{ID_MODEL_FROM_DATABASE}=="*[Hh]eadphone*", ENV{SOUND_FORM_FACTOR}="headphone", GOTO="sound_end"
|
||||
|
||||
ENV{ID_MODEL}=="*[Hh]eadset*", ENV{SOUND_FORM_FACTOR}="headset", GOTO="sound_end"
|
||||
ENV{ID_MODEL_FROM_DATABASE}=="*[Hh]eadset*", ENV{SOUND_FORM_FACTOR}="headset", GOTO="sound_end"
|
||||
|
||||
ENV{ID_MODEL}=="*[Hh]andset*", ENV{SOUND_FORM_FACTOR}="handset", GOTO="sound_end"
|
||||
ENV{ID_MODEL_FROM_DATABASE}=="*[Hh]andset*", ENV{SOUND_FORM_FACTOR}="handset", GOTO="sound_end"
|
||||
|
||||
ENV{ID_MODEL}=="*[Mm]icrophone*", ENV{SOUND_FORM_FACTOR}="microphone", GOTO="sound_end"
|
||||
ENV{ID_MODEL_FROM_DATABASE}=="*[Mm]icrophone*", ENV{SOUND_FORM_FACTOR}="microphone", GOTO="sound_end"
|
||||
|
||||
LABEL="sound_end"
|
||||
@@ -0,0 +1,17 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/80-drivers.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION!="add", GOTO="drivers_end"
|
||||
|
||||
ENV{MODALIAS}=="?*", RUN{builtin}+="kmod load '$env{MODALIAS}'"
|
||||
SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="SD", RUN{builtin}+="kmod load tifm_sd"
|
||||
SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="MS", RUN{builtin}+="kmod load tifm_ms"
|
||||
SUBSYSTEM=="memstick", RUN{builtin}+="kmod load ms_block mspro_block"
|
||||
SUBSYSTEM=="i2o", RUN{builtin}+="kmod load i2o_block"
|
||||
SUBSYSTEM=="module", KERNEL=="parport_pc", RUN{builtin}+="kmod load ppdev"
|
||||
KERNEL=="mtd*ro", ENV{MTD_FTL}=="smartmedia", RUN{builtin}+="kmod load sm_ftl"
|
||||
|
||||
LABEL="drivers_end"
|
||||
@@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/80-net-name-slot.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION!="add", GOTO="net_name_slot_end"
|
||||
SUBSYSTEM!="net", GOTO="net_name_slot_end"
|
||||
NAME!="", GOTO="net_name_slot_end"
|
||||
|
||||
IMPORT{cmdline}="net.ifnames"
|
||||
ENV{net.ifnames}=="0", GOTO="net_name_slot_end"
|
||||
|
||||
NAME=="", ENV{ID_NET_NAME_ONBOARD}!="", NAME="$env{ID_NET_NAME_ONBOARD}"
|
||||
NAME=="", ENV{ID_NET_NAME_SLOT}!="", NAME="$env{ID_NET_NAME_SLOT}"
|
||||
NAME=="", ENV{ID_NET_NAME_PATH}!="", NAME="$env{ID_NET_NAME_PATH}"
|
||||
|
||||
LABEL="net_name_slot_end"
|
||||
@@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Derived from eudev 3.2.14 rules/81-net-dhcp.rules
|
||||
# Modifications by the Finit project: none
|
||||
#
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="net_dhcp_end"
|
||||
SUBSYSTEM!="net", GOTO="net_dhcp_end"
|
||||
|
||||
# Network interfaces requiring DHCPOFFER messages to be broadcast
|
||||
# must set ID_NET_DHCP_BROADCAST to "1". This property will be
|
||||
# checked by the networkd DHCP4 client to set the DHCP option
|
||||
|
||||
# s390 ccwgroup interfaces in layer3 mode need broadcast DHCPOFFER
|
||||
# using the link driver to detect this condition
|
||||
ENV{ID_NET_DRIVER}=="qeth_l3", ENV{ID_NET_DHCP_BROADCAST}="1"
|
||||
|
||||
LABEL="net_dhcp_end"
|
||||
+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,251 @@
|
||||
/* 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"
|
||||
|
||||
/*
|
||||
* 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 E: records for any devpath, keyed from its sysfs attributes:
|
||||
* the device has no uevent in hand, so major:minor, ifindex, and
|
||||
* subsystem come from /sys. Serves IMPORT{parent} and the D-Bus
|
||||
* Info method.
|
||||
*/
|
||||
int udevdb_read_devpath(const char *devpath, struct uevent *ev)
|
||||
{
|
||||
char syspath[PATH_MAX], dbfile[PATH_MAX];
|
||||
char subsys[64] = "unknown";
|
||||
char attr[64];
|
||||
const char *sn;
|
||||
|
||||
if (!devpath || *devpath != '/')
|
||||
return -1;
|
||||
|
||||
sn = strrchr(devpath, '/');
|
||||
sn = sn ? sn + 1 : devpath;
|
||||
|
||||
snprintf(syspath, sizeof(syspath), "/sys%s", devpath);
|
||||
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);
|
||||
}
|
||||
|
||||
int udevdb_read_parent(struct uevent *ev)
|
||||
{
|
||||
char ppath[PATH_MAX];
|
||||
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;
|
||||
|
||||
return udevdb_read_devpath(ppath, 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,77 @@
|
||||
/* 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);
|
||||
|
||||
/*
|
||||
* Like udevdb_read(), for any devpath, keyed from sysfs.
|
||||
*/
|
||||
int udevdb_read_devpath(const char *devpath, 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:
|
||||
*/
|
||||
+1221
File diff suppressed because it is too large.
Load diff
@@ -152,6 +152,13 @@ static void emit_property(struct xbuf *x, const link_property_t *p)
|
||||
p->name, p->sig ? p->sig : "s");
|
||||
}
|
||||
|
||||
static void emit_signal(struct xbuf *x, const link_signal_t *s)
|
||||
{
|
||||
xprintf(x, " <signal name=\"%s\">\n", s->name);
|
||||
emit_args(x, s->sig, "out");
|
||||
xprintf(x, " </signal>\n");
|
||||
}
|
||||
|
||||
static const char STANDARD_INTERFACES_XML[] =
|
||||
" <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
|
||||
" <method name=\"Introspect\">\n"
|
||||
@@ -174,6 +181,28 @@ static const char STANDARD_INTERFACES_XML[] =
|
||||
" <arg name=\"interface\" type=\"s\" direction=\"in\"/>\n"
|
||||
" <arg name=\"props\" type=\"a{sv}\" direction=\"out\"/>\n"
|
||||
" </method>\n"
|
||||
" <signal name=\"PropertiesChanged\">\n"
|
||||
" <arg name=\"interface\" type=\"s\"/>\n"
|
||||
" <arg name=\"changed\" type=\"a{sv}\"/>\n"
|
||||
" <arg name=\"invalidated\" type=\"as\"/>\n"
|
||||
" </signal>\n"
|
||||
" </interface>\n";
|
||||
|
||||
/*
|
||||
* Hello/AddMatch/RemoveMatch are answered on the canonical
|
||||
* /org/freedesktop/DBus object only, so declare them only there.
|
||||
*/
|
||||
static const char DBUS_DRIVER_XML[] =
|
||||
" <interface name=\"org.freedesktop.DBus\">\n"
|
||||
" <method name=\"Hello\">\n"
|
||||
" <arg name=\"name\" type=\"s\" direction=\"out\"/>\n"
|
||||
" </method>\n"
|
||||
" <method name=\"AddMatch\">\n"
|
||||
" <arg name=\"rule\" type=\"s\" direction=\"in\"/>\n"
|
||||
" </method>\n"
|
||||
" <method name=\"RemoveMatch\">\n"
|
||||
" <arg name=\"rule\" type=\"s\" direction=\"in\"/>\n"
|
||||
" </method>\n"
|
||||
" </interface>\n";
|
||||
|
||||
/* Is `child` a path under `parent`? If so, write the first segment
|
||||
@@ -219,6 +248,8 @@ static int handle_introspect(link_connection_t *conn, const struct link_msg *m)
|
||||
"<node>\n");
|
||||
|
||||
xprintf(&x, "%s", STANDARD_INTERFACES_XML);
|
||||
if (!strcmp(path, "/org/freedesktop/DBus"))
|
||||
xprintf(&x, "%s", DBUS_DRIVER_XML);
|
||||
|
||||
o = NULL;
|
||||
{
|
||||
@@ -238,6 +269,7 @@ static int handle_introspect(link_connection_t *conn, const struct link_msg *m)
|
||||
|
||||
TAILQ_FOREACH(e, &o->vtables, link) {
|
||||
const link_property_t *prop;
|
||||
const link_signal_t *sig;
|
||||
|
||||
xprintf(&x, " <interface name=\"%s\">\n",
|
||||
e->vt->interface);
|
||||
@@ -247,6 +279,9 @@ static int handle_introspect(link_connection_t *conn, const struct link_msg *m)
|
||||
if (e->vt->properties)
|
||||
for (prop = e->vt->properties; prop->name; prop++)
|
||||
emit_property(&x, prop);
|
||||
if (e->vt->signals)
|
||||
for (sig = e->vt->signals; sig->name; sig++)
|
||||
emit_signal(&x, sig);
|
||||
xprintf(&x, " </interface>\n");
|
||||
}
|
||||
}
|
||||
|
||||
+72
-9
@@ -261,6 +261,7 @@ const char *link_call_path (const link_call_t *c) { return c ? c->incoming.p
|
||||
const char *link_call_interface(const link_call_t *c) { return c ? c->incoming.interface : NULL; }
|
||||
const char *link_call_member (const link_call_t *c) { return c ? c->incoming.member : NULL; }
|
||||
uid_t link_call_uid (const link_call_t *c) { return c ? c->uid : LINK_UID_UNKNOWN; }
|
||||
link_connection_t *link_call_connection(const link_call_t *c) { return c ? c->conn : NULL; }
|
||||
|
||||
link_writer_t *link_call_reply(link_call_t *call)
|
||||
{
|
||||
@@ -287,6 +288,7 @@ int link_call_reply_error(link_call_t *call, const char *name, const char *messa
|
||||
int link_call_read_byte (link_call_t *c, uint8_t *o) { return __r_byte (&c->read_cursor, o); }
|
||||
int link_call_read_bool (link_call_t *c, int *o) { return __r_bool (&c->read_cursor, o); }
|
||||
int link_call_read_u32 (link_call_t *c, uint32_t *o) { return __r_u32 (&c->read_cursor, o); }
|
||||
int link_call_read_u64 (link_call_t *c, uint64_t *o) { return __r_u64 (&c->read_cursor, o); }
|
||||
int link_call_read_string(link_call_t *c, const char **o) { return __r_string(&c->read_cursor, o); }
|
||||
int link_call_read_path (link_call_t *c, const char **o) { return __r_path (&c->read_cursor, o); }
|
||||
|
||||
@@ -298,6 +300,7 @@ ssize_t link_writer_finish(link_writer_t *w) { return
|
||||
void link_w_byte (link_writer_t *w, uint8_t v) { __w_byte(w, v); }
|
||||
void link_w_bool (link_writer_t *w, int v) { __w_bool(w, v); }
|
||||
void link_w_u32 (link_writer_t *w, uint32_t v) { __w_u32(w, v); }
|
||||
void link_w_u64 (link_writer_t *w, uint64_t v) { __w_u64(w, v); }
|
||||
void link_w_string (link_writer_t *w, const char *s) { __w_string(w, s); }
|
||||
void link_w_path (link_writer_t *w, const char *s) { __w_path(w, s); }
|
||||
void link_w_variant_string(link_writer_t *w, const char *s) { __w_variant_string(w, s); }
|
||||
@@ -312,6 +315,7 @@ void link_reader_init(link_reader_t *r, const uint8_t *body, size_t len) { __r_i
|
||||
int link_r_byte (link_reader_t *r, uint8_t *o) { return __r_byte (r, o); }
|
||||
int link_r_bool (link_reader_t *r, int *o) { return __r_bool (r, o); }
|
||||
int link_r_u32 (link_reader_t *r, uint32_t *o) { return __r_u32 (r, o); }
|
||||
int link_r_u64 (link_reader_t *r, uint64_t *o) { return __r_u64 (r, o); }
|
||||
int link_r_string(link_reader_t *r, const char **o) { return __r_string(r, o); }
|
||||
int link_r_path (link_reader_t *r, const char **o) { return __r_path (r, o); }
|
||||
int link_r_variant_begin (link_reader_t *r, char *type) { return __r_variant_begin(r, type); }
|
||||
@@ -390,6 +394,7 @@ static struct link_parked *park(link_connection_t *conn, const uint8_t *frame,
|
||||
bus->parked[i].tok = ++bus->next_tok;
|
||||
bus->parked[i].conn = conn;
|
||||
bus->parked[i].stamp = __now_ms();
|
||||
bus->parked[i].uid = (uid_t)-1;
|
||||
bus->parked[i].len = len;
|
||||
memcpy(bus->parked[i].buf, frame, len);
|
||||
*tok = bus->parked[i].tok;
|
||||
@@ -474,13 +479,20 @@ void __dispatch_forget_conn(link_connection_t *conn)
|
||||
|
||||
static int dispatch_call(link_connection_t *conn, const struct link_msg *m,
|
||||
const uint8_t *frame, size_t framelen,
|
||||
const uid_t *known_uid);
|
||||
const uid_t *known_uid, int resumed);
|
||||
|
||||
void link_uid_resolved(link_connection_t *conn, link_authz_t tok, uid_t uid)
|
||||
/*
|
||||
* Shared resume: copy the parked frame out, free the slot, re-enter
|
||||
* dispatch. `uid` NULL means "use what the slot recorded" (a
|
||||
* handler-parked call); non-NULL is a resolver's answer.
|
||||
*/
|
||||
static void resume_parked(link_connection_t *conn, link_authz_t tok,
|
||||
const uid_t *uid, int resumed)
|
||||
{
|
||||
uint8_t buf[LINK_PARKED_MSG_MAX];
|
||||
struct link_parked *p = NULL;
|
||||
struct link_msg msg;
|
||||
uid_t slot_uid;
|
||||
size_t len;
|
||||
int i;
|
||||
|
||||
@@ -498,15 +510,58 @@ void link_uid_resolved(link_connection_t *conn, link_authz_t tok, uid_t uid)
|
||||
|
||||
/* Copy the message out and free the slot before dispatching:
|
||||
* the handler may park a call of its own. */
|
||||
len = p->len;
|
||||
len = p->len;
|
||||
slot_uid = p->uid;
|
||||
memcpy(buf, p->buf, len);
|
||||
unpark(p);
|
||||
|
||||
if (__msg_parse(buf, len, &msg) <= 0)
|
||||
return;
|
||||
|
||||
__dbg("resumed %s, caller uid %d", msg.member ? msg.member : "call", (int)uid);
|
||||
(void)dispatch_call(conn, &msg, NULL, 0, &uid);
|
||||
if (!uid)
|
||||
uid = &slot_uid;
|
||||
|
||||
__dbg("resumed %s, caller uid %d", msg.member ? msg.member : "call", (int)*uid);
|
||||
(void)dispatch_call(conn, &msg, NULL, 0, uid, resumed);
|
||||
}
|
||||
|
||||
void link_uid_resolved(link_connection_t *conn, link_authz_t tok, uid_t uid)
|
||||
{
|
||||
resume_parked(conn, tok, &uid, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Defer the reply: hold the request and re-run the handler later via
|
||||
* link_call_resume(), where link_call_resumed() reads true. A resumed
|
||||
* call cannot park again -- the raw frame is gone -- so the handler
|
||||
* must answer on the second run. link_connection_expire() is the
|
||||
* backstop for a resume that never comes.
|
||||
*/
|
||||
int link_call_park(link_call_t *call, link_authz_t *tok)
|
||||
{
|
||||
struct link_parked *p;
|
||||
|
||||
if (!call || !tok || call->resumed)
|
||||
return -1;
|
||||
|
||||
p = park(call->conn, call->frame, call->framelen, tok);
|
||||
if (!p)
|
||||
return -1;
|
||||
|
||||
p->uid = call->uid;
|
||||
call->parked = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void link_call_resume(link_connection_t *conn, link_authz_t tok)
|
||||
{
|
||||
resume_parked(conn, tok, NULL, 1);
|
||||
}
|
||||
|
||||
int link_call_resumed(const link_call_t *call)
|
||||
{
|
||||
return call ? call->resumed : 0;
|
||||
}
|
||||
|
||||
int __dispatch_message(link_connection_t *conn, const struct link_msg *m, size_t framelen)
|
||||
@@ -521,7 +576,7 @@ int __dispatch_message(link_connection_t *conn, const struct link_msg *m, size_t
|
||||
return 0;
|
||||
}
|
||||
|
||||
return dispatch_call(conn, m, conn->rxbuf, framelen, NULL);
|
||||
return dispatch_call(conn, m, conn->rxbuf, framelen, NULL, 0);
|
||||
}
|
||||
|
||||
/* Who may invoke a privileged method. Without an authorizer, only
|
||||
@@ -579,7 +634,7 @@ static int resolve_caller(link_connection_t *conn, const struct link_msg *m,
|
||||
|
||||
static int dispatch_call(link_connection_t *conn, const struct link_msg *m,
|
||||
const uint8_t *frame, size_t framelen,
|
||||
const uid_t *known_uid)
|
||||
const uid_t *known_uid, int resumed)
|
||||
{
|
||||
struct link_object *o;
|
||||
struct link_vtable_entry *e = NULL;
|
||||
@@ -673,8 +728,8 @@ static int dispatch_call(link_connection_t *conn, const struct link_msg *m,
|
||||
* resolved over a broker carries only its uid, so the
|
||||
* authorizer sees no groups and falls back to root-only. */
|
||||
if (!caller_may(conn->server, call_uid,
|
||||
known_uid ? NULL : conn->peer_groups,
|
||||
known_uid ? 0 : conn->peer_ngroups)) {
|
||||
conn->broker ? NULL : conn->peer_groups,
|
||||
conn->broker ? 0 : conn->peer_ngroups)) {
|
||||
__dbg("denied %s, caller uid %d is not privileged",
|
||||
m->member, (int)call_uid);
|
||||
return __send_error(conn, m,
|
||||
@@ -687,10 +742,18 @@ static int dispatch_call(link_connection_t *conn, const struct link_msg *m,
|
||||
call.conn = conn;
|
||||
call.uid = call_uid;
|
||||
call.incoming = *m;
|
||||
call.frame = frame;
|
||||
call.framelen = framelen;
|
||||
call.resumed = resumed;
|
||||
__r_init(&call.read_cursor, m->body, m->body_avail);
|
||||
|
||||
rc = meth->handler(&call, e->userdata);
|
||||
|
||||
/* The handler parked the call: no reply yet, it is resumed via
|
||||
* link_call_resume() or expired by link_connection_expire(). */
|
||||
if (call.parked)
|
||||
return 0;
|
||||
|
||||
/* Every path returns the status of whatever it put on the wire,
|
||||
* so a failed send propagates out and the read loop drops the
|
||||
* peer rather than leaving a half-written frame on a live link.
|
||||
|
||||
+6
-1
@@ -40,7 +40,7 @@ typedef enum {
|
||||
* than a number per call site. */
|
||||
#define LINK_CALL_HDR_MAX 1024
|
||||
#define LINK_CALL_BODY_MAX 1024
|
||||
#define LINK_PARKED_CAP 4 /* inbound calls awaiting a uid */
|
||||
#define LINK_PARKED_CAP 4 /* inbound calls held for later */
|
||||
/* A call parked for authorization is a privileged one: an object path
|
||||
* and at most a service name. Finit's per-service paths alone run to
|
||||
* 512 bytes, so leave room for the header around one. Anything that
|
||||
@@ -74,6 +74,7 @@ struct link_parked {
|
||||
link_authz_t tok;
|
||||
link_connection_t *conn;
|
||||
uint64_t stamp;
|
||||
uid_t uid; /* caller, for handler-parked calls */
|
||||
size_t len;
|
||||
uint8_t buf[LINK_PARKED_MSG_MAX];
|
||||
};
|
||||
@@ -133,6 +134,10 @@ struct link_call {
|
||||
int reply_consumed;
|
||||
int error_sent;
|
||||
uid_t uid; /* caller, resolved for a broker peer */
|
||||
const uint8_t *frame; /* raw frame, for link_call_park() */
|
||||
size_t framelen;
|
||||
int parked; /* handler deferred its reply */
|
||||
int resumed; /* re-run via link_call_resume() */
|
||||
};
|
||||
|
||||
/* A parsed AddMatch rule. Fields are NULL when the rule omits the
|
||||
|
||||
@@ -155,6 +155,19 @@ void link_server_set_authorizer(link_server_t *server, link_authorizer_t cb,
|
||||
* asked about; a reply callback is handed it as its first argument. */
|
||||
void link_uid_resolved(link_connection_t *conn, link_authz_t tok, uid_t uid);
|
||||
|
||||
/* ---------- handler-deferred replies ---------- */
|
||||
|
||||
/* A handler that cannot answer yet parks the call and returns 0
|
||||
* without replying; the framework sends nothing. Resume re-runs the
|
||||
* handler with link_call_resumed() reading true, and this time it
|
||||
* must reply -- a resumed call cannot park again. Parked calls that
|
||||
* are never resumed are expired by link_connection_expire() with a
|
||||
* TimedOut error. Room is limited (four per connection); a failed
|
||||
* park means answer now. */
|
||||
int link_call_park (link_call_t *call, link_authz_t *tok);
|
||||
void link_call_resume (link_connection_t *conn, link_authz_t tok);
|
||||
int link_call_resumed(const link_call_t *call);
|
||||
|
||||
/* Called with the reply to an outbound link_connection_call(). `reply`
|
||||
* is NULL if the connection dropped before one arrived. */
|
||||
typedef void (*link_reply_cb_t)(link_connection_t *conn, const link_reply_t *reply,
|
||||
@@ -247,10 +260,20 @@ typedef struct {
|
||||
link_property_getter_fn getter;
|
||||
} link_property_t;
|
||||
|
||||
/*
|
||||
* Declares a signal for introspection only; emission is unchecked,
|
||||
* see link_connection_emit_signal().
|
||||
*/
|
||||
typedef struct {
|
||||
const char *name; /* member name */
|
||||
const char *sig; /* D-Bus signature, e.g. "sss" */
|
||||
} link_signal_t;
|
||||
|
||||
typedef struct {
|
||||
const char *interface; /* e.g. "org.finit.Manager1" */
|
||||
const link_method_t *methods; /* terminated by {NULL, ...}, or NULL */
|
||||
const link_property_t *properties; /* terminated by {NULL, ...}, or NULL */
|
||||
const link_signal_t *signals; /* terminated by {NULL, ...}, or NULL */
|
||||
} link_vtable_t;
|
||||
|
||||
/* Register one (interface, methods) at `path`. Calling repeatedly
|
||||
@@ -270,6 +293,7 @@ const char *link_call_path (const link_call_t *call);
|
||||
const char *link_call_interface(const link_call_t *call);
|
||||
const char *link_call_member (const link_call_t *call);
|
||||
uid_t link_call_uid (const link_call_t *call);
|
||||
link_connection_t *link_call_connection(const link_call_t *call);
|
||||
|
||||
/* ---------- reading method-call arguments ----------
|
||||
*
|
||||
@@ -283,6 +307,7 @@ uid_t link_call_uid (const link_call_t *call);
|
||||
int link_call_read_byte (link_call_t *call, uint8_t *out);
|
||||
int link_call_read_bool (link_call_t *call, int *out);
|
||||
int link_call_read_u32 (link_call_t *call, uint32_t *out);
|
||||
int link_call_read_u64 (link_call_t *call, uint64_t *out);
|
||||
int link_call_read_string(link_call_t *call, const char **out); /* "s" */
|
||||
int link_call_read_path (link_call_t *call, const char **out); /* "o" */
|
||||
|
||||
@@ -424,6 +449,7 @@ ssize_t link_writer_finish(link_writer_t *w);
|
||||
void link_w_byte (link_writer_t *w, uint8_t v);
|
||||
void link_w_bool (link_writer_t *w, int v);
|
||||
void link_w_u32 (link_writer_t *w, uint32_t v);
|
||||
void link_w_u64 (link_writer_t *w, uint64_t v); /* "t" */
|
||||
void link_w_string (link_writer_t *w, const char *s); /* "s" */
|
||||
void link_w_path (link_writer_t *w, const char *s); /* "o" */
|
||||
void link_w_variant_string(link_writer_t *w, const char *s); /* "v" containing "s" */
|
||||
@@ -441,6 +467,7 @@ void link_reader_init(link_reader_t *r, const uint8_t *body, size_t len);
|
||||
int link_r_byte (link_reader_t *r, uint8_t *out);
|
||||
int link_r_bool (link_reader_t *r, int *out);
|
||||
int link_r_u32 (link_reader_t *r, uint32_t *out);
|
||||
int link_r_u64 (link_reader_t *r, uint64_t *out);
|
||||
int link_r_string (link_reader_t *r, const char **out); /* "s" */
|
||||
int link_r_path (link_reader_t *r, const char **out); /* "o" */
|
||||
int link_r_variant_begin (link_reader_t *r, char *type); /* sig header, cursor at value */
|
||||
|
||||
@@ -79,6 +79,14 @@ void __w_u32(struct link_writer *w, uint32_t v)
|
||||
put_u32(w, v);
|
||||
}
|
||||
|
||||
void __w_u64(struct link_writer *w, uint64_t v)
|
||||
{
|
||||
if (reserve(w, 8, 8) < 0)
|
||||
return;
|
||||
put_u32(w, (uint32_t)(v & 0xffffffffu));
|
||||
put_u32(w, (uint32_t)(v >> 32));
|
||||
}
|
||||
|
||||
static void write_lenprefixed(struct link_writer *w, const char *s, int onebyte_len)
|
||||
{
|
||||
size_t len = s ? strlen(s) : 0;
|
||||
@@ -230,6 +238,18 @@ int __r_u32(struct link_reader *r, uint32_t *out)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __r_u64(struct link_reader *r, uint64_t *out)
|
||||
{
|
||||
if (r_skip_align(r, 8) < 0 || r->off + 8 > r->cap) {
|
||||
r->err = 1;
|
||||
return -1;
|
||||
}
|
||||
*out = (uint64_t)rd_u32(r->base + r->off)
|
||||
| ((uint64_t)rd_u32(r->base + r->off + 4) << 32);
|
||||
r->off += 8;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __r_bool(struct link_reader *r, int *out)
|
||||
{
|
||||
uint32_t v;
|
||||
@@ -327,6 +347,13 @@ int __r_skip_basic(struct link_reader *r, char type)
|
||||
case 'u':
|
||||
case 'i':
|
||||
return __r_u32(r, &u);
|
||||
case 't':
|
||||
case 'x':
|
||||
case 'd': {
|
||||
uint64_t t;
|
||||
|
||||
return __r_u64(r, &t);
|
||||
}
|
||||
case 'y':
|
||||
return __r_byte(r, &y);
|
||||
default:
|
||||
@@ -396,6 +423,9 @@ ssize_t __marshal_va(uint8_t *body, size_t cap, const char *sig, va_list ap)
|
||||
case 'u':
|
||||
__w_u32(&w, va_arg(ap, uint32_t));
|
||||
break;
|
||||
case 't':
|
||||
__w_u64(&w, va_arg(ap, uint64_t));
|
||||
break;
|
||||
case 's':
|
||||
__w_string(&w, va_arg(ap, const char *));
|
||||
break;
|
||||
|
||||
@@ -22,6 +22,7 @@ ssize_t __w_finish(struct link_writer *w);
|
||||
void __w_byte (struct link_writer *w, uint8_t v);
|
||||
void __w_bool (struct link_writer *w, int v);
|
||||
void __w_u32 (struct link_writer *w, uint32_t v);
|
||||
void __w_u64 (struct link_writer *w, uint64_t v);
|
||||
void __w_string (struct link_writer *w, const char *s); /* "s" */
|
||||
void __w_path (struct link_writer *w, const char *s); /* "o" */
|
||||
void __w_sig (struct link_writer *w, const char *s); /* "g" */
|
||||
@@ -44,6 +45,7 @@ void __r_init (struct link_reader *r, const uint8_t *body, size_t len);
|
||||
int __r_byte (struct link_reader *r, uint8_t *out);
|
||||
int __r_bool (struct link_reader *r, int *out);
|
||||
int __r_u32 (struct link_reader *r, uint32_t *out);
|
||||
int __r_u64 (struct link_reader *r, uint64_t *out);
|
||||
int __r_string(struct link_reader *r, const char **out); /* "s" */
|
||||
int __r_path (struct link_reader *r, const char **out); /* "o" */
|
||||
int __r_variant_begin (struct link_reader *r, char *type); /* sig header, cursor at value */
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
dist_man8_MANS = finit.8 initctl.8
|
||||
dist_man5_MANS = finit.conf.5
|
||||
|
||||
if KEVENTD
|
||||
dist_man8_MANS += keventd.8
|
||||
endif
|
||||
@@ -19,6 +19,10 @@ is the official tool for interacting with
|
||||
.Xr finit 8 .
|
||||
It comes with functions for showing status, querying health of services,
|
||||
as well as editing, enabling and disabling services.
|
||||
Commands are sent over the Finit D-Bus API when available, with the
|
||||
legacy
|
||||
.Pa INIT_SOCKET
|
||||
protocol as fallback.
|
||||
.Sh OPTIONS
|
||||
This program follows the usual UNIX command line syntax, with long
|
||||
options starting with two dashes (`-'). The options are as follows:
|
||||
@@ -205,6 +209,10 @@ restricted
|
||||
Send signal S to service, by name.
|
||||
.Cm S
|
||||
may be a complete signal name such as SIGHUP, or short name such as HUP, or the signal number such as 1 (SIGHUP)
|
||||
.It Nm Ar monitor
|
||||
Stream service state, runlevel, and condition change signals from the
|
||||
Finit D-Bus API until interrupted. Only available when Finit is built
|
||||
with D-Bus support
|
||||
.It Nm Ar restart Cm NAME[:ID]
|
||||
Restart (stop/start) service by name
|
||||
.It Nm Ar status Cm NAME[:ID]
|
||||
|
||||
+368
@@ -0,0 +1,368 @@
|
||||
.\" Hey, EMACS: -*- nroff -*-
|
||||
.\" First parameter, NAME, should be all caps
|
||||
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
|
||||
.\" other parameters are allowed: see man(7), man(1)
|
||||
.Dd Aug 16, 2026
|
||||
.\" Please adjust this date whenever revising the manpage.
|
||||
.Dt KEVENTD 8 SMM
|
||||
.Os Linux
|
||||
.Sh NAME
|
||||
.Nm keventd
|
||||
.Nd Finit device manager daemon
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl c
|
||||
.Op Fl d
|
||||
.Op Fl g Ar group
|
||||
.Op Fl G
|
||||
.Op Fl h
|
||||
.Op Fl n
|
||||
.Op Fl p
|
||||
.Op Fl r Ar dir
|
||||
.Op Fl S
|
||||
.Op Fl t Ar sec
|
||||
.Op Fl v
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a built-in device manager bundled with
|
||||
.Xr finit 8 .
|
||||
It listens for kernel uevents on a
|
||||
.Dv NETLINK_KOBJECT_UEVENT
|
||||
socket and handles device node creation, persistent symlinks, firmware
|
||||
loading, kernel module loading, and condition management. Events are
|
||||
matched against udev rules read from
|
||||
.Pa /lib/udev/rules.d ,
|
||||
.Pa /run/udev/rules.d ,
|
||||
and
|
||||
.Pa /etc/udev/rules.d ,
|
||||
and device properties are persisted to
|
||||
.Pa /run/udev/data
|
||||
in the format libudev consumers expect.
|
||||
.Pp
|
||||
It replaces the need for external device managers like
|
||||
.Nm mdev ,
|
||||
.Nm mdevd ,
|
||||
or
|
||||
.Nm udevd
|
||||
on systems where a lighter-weight solution is preferred, particularly
|
||||
on embedded systems.
|
||||
.Sh OPTIONS
|
||||
.Bl -tag -width Ds
|
||||
.It Fl c
|
||||
Run coldplug at startup. Walks the
|
||||
.Pa /sys/devices
|
||||
tree and writes
|
||||
.Cm add
|
||||
to each
|
||||
.Pa uevent
|
||||
file, causing the kernel to re-emit add events for all devices already
|
||||
present. This populates
|
||||
.Pa /dev
|
||||
with nodes for hardware that existed before
|
||||
.Nm
|
||||
started.
|
||||
.It Fl d
|
||||
Enable debug mode. Implies
|
||||
.Fl n .
|
||||
All received uevents are logged to stderr.
|
||||
.It Fl h
|
||||
Show help text and exit.
|
||||
.It Fl g Ar group
|
||||
Override the netlink multicast group mask used for uevent rebroadcast.
|
||||
The default is 4 (group 0x4), which is the
|
||||
.Sy libudev-zero
|
||||
convention.
|
||||
Bit 0 is always masked out to prevent a feedback loop with the kernel
|
||||
group.
|
||||
See
|
||||
.Sx NETLINK REBROADCAST
|
||||
below.
|
||||
.It Fl G
|
||||
Disable netlink uevent rebroadcast entirely.
|
||||
See
|
||||
.Sx NETLINK REBROADCAST
|
||||
below.
|
||||
.It Fl n
|
||||
Run in foreground, do not daemonize. Log messages are written to
|
||||
stderr instead of syslog.
|
||||
.It Fl p
|
||||
Passive mode: monitor power supply events only, leaving device
|
||||
management to an external device manager.
|
||||
.It Fl r Ar dir
|
||||
Extra udev rules directory, searched after the standard paths.
|
||||
.It Fl S
|
||||
Settle: wait until the device event queue is quiet, then exit 0, or
|
||||
non-zero on timeout. Asks the running
|
||||
.Nm
|
||||
over D-Bus when available, else polls the kernel's uevent sequence
|
||||
counter.
|
||||
.It Fl t Ar sec
|
||||
Settle timeout in seconds, default 30. Only with
|
||||
.Fl S .
|
||||
.It Fl v
|
||||
Show version and exit.
|
||||
.El
|
||||
.Sh DEVICE NODES
|
||||
On receiving an
|
||||
.Cm add
|
||||
event with
|
||||
.Cm MAJOR ,
|
||||
.Cm MINOR ,
|
||||
and
|
||||
.Cm DEVNAME
|
||||
fields,
|
||||
.Nm
|
||||
creates the device node in
|
||||
.Pa /dev
|
||||
using
|
||||
.Xr mknod 2 .
|
||||
Parent directories are created as needed, e.g.\&
|
||||
.Pa /dev/input/
|
||||
for
|
||||
.Pa /dev/input/event0 .
|
||||
.Pp
|
||||
On
|
||||
.Cm remove
|
||||
events the device node and its associated symlinks are cleaned up.
|
||||
.Pp
|
||||
Permissions are assigned based on built-in rules matching on device
|
||||
subsystem and name. For example, block devices default to mode 0660
|
||||
owned by root:disk, TTY devices to root:tty, and common devices like
|
||||
.Pa /dev/null
|
||||
and
|
||||
.Pa /dev/zero
|
||||
are world-readable (0666).
|
||||
.Sh PERSISTENT SYMLINKS
|
||||
For block devices,
|
||||
.Nm
|
||||
creates symlinks in
|
||||
.Pa /dev/disk/ :
|
||||
.Bl -tag -width by-path -offset indent -compact
|
||||
.It Pa by-id
|
||||
Based on device serial number and model, read from sysfs.
|
||||
.It Pa by-path
|
||||
Based on the device topology path.
|
||||
.El
|
||||
.Pp
|
||||
For input devices, symlinks are created in
|
||||
.Pa /dev/input/ :
|
||||
.Bl -tag -width by-path -offset indent -compact
|
||||
.It Pa by-id
|
||||
Based on the device name from sysfs.
|
||||
.It Pa by-path
|
||||
Based on the physical device path.
|
||||
.El
|
||||
.Pp
|
||||
Symlinks are tracked internally and automatically removed when the
|
||||
device is unplugged.
|
||||
.Sh FIRMWARE LOADING
|
||||
When a kernel driver requests firmware via
|
||||
.Fn request_firmware ,
|
||||
the kernel emits a uevent with a
|
||||
.Cm FIRMWARE
|
||||
field.
|
||||
.Nm
|
||||
handles this by searching for the firmware file in the following order:
|
||||
.Pp
|
||||
.Bl -enum -compact -offset indent
|
||||
.It
|
||||
.Pa /lib/firmware/updates/<kernel-version>/<name>
|
||||
.It
|
||||
.Pa /lib/firmware/updates/<name>
|
||||
.It
|
||||
.Pa /lib/firmware/<kernel-version>/<name>
|
||||
.It
|
||||
.Pa /lib/firmware/<name>
|
||||
.El
|
||||
.Pp
|
||||
The firmware is loaded by writing to the device's sysfs
|
||||
.Pa loading
|
||||
and
|
||||
.Pa data
|
||||
attributes.
|
||||
.Sh MODULE LOADING
|
||||
When a device add event includes a
|
||||
.Cm MODALIAS
|
||||
field,
|
||||
.Nm
|
||||
spawns
|
||||
.Cm modprobe -bq
|
||||
to load the matching kernel module. Module loading is asynchronous
|
||||
to avoid blocking event processing.
|
||||
.Sh CONDITIONS
|
||||
.Nm
|
||||
provides conditions for Finit's dependency system by creating and
|
||||
removing symlinks in
|
||||
.Pa /run/finit/cond/ .
|
||||
.Ss Device Conditions
|
||||
When a device node is created,
|
||||
.Nm
|
||||
asserts a corresponding
|
||||
.Cm dev/
|
||||
condition. For example, creating
|
||||
.Pa /dev/sda
|
||||
asserts
|
||||
.Cm dev/sda .
|
||||
This allows services to depend on specific devices:
|
||||
.Bd -literal -offset indent
|
||||
service mdadm {
|
||||
description = "RAID monitor"
|
||||
runlevel = "2345"
|
||||
conditions = { "dev/sda" }
|
||||
command = "/usr/sbin/mdadm"
|
||||
}
|
||||
|
||||
service gpsd {
|
||||
description = "GPS daemon"
|
||||
runlevel = "2345"
|
||||
conditions = { "dev/ttyUSB0" }
|
||||
command = "/usr/sbin/gpsd"
|
||||
}
|
||||
.Ed
|
||||
.Pp
|
||||
When the device is removed, the condition is cleared and Finit stops
|
||||
the dependent services.
|
||||
.Ss Power Supply Conditions
|
||||
.Nm
|
||||
monitors the
|
||||
.Cm power_supply
|
||||
subsystem and provides:
|
||||
.Bl -tag -width sys/pwr/ac -offset indent -compact
|
||||
.It Cm sys/pwr/ac
|
||||
Asserted when AC power is connected.
|
||||
.El
|
||||
.Pp
|
||||
Useful for preventing power-hungry services from running on battery:
|
||||
.Bd -literal -offset indent
|
||||
service cron {
|
||||
description = "Cron daemon"
|
||||
runlevel = "2345"
|
||||
conditions = { "sys/pwr/ac" }
|
||||
command = "cron -f"
|
||||
}
|
||||
.Ed
|
||||
.Ss Class and Driver Conditions
|
||||
Devices without a
|
||||
.Pa /dev
|
||||
node are covered by
|
||||
.Cm class/<subsystem>/<name> ,
|
||||
asserted on every sysfs class device add, e.g.\&
|
||||
.Cm class/leds/blue
|
||||
or
|
||||
.Cm class/net/eth0 .
|
||||
.Cm driver/<name>
|
||||
is asserted while the driver
|
||||
.Ar name
|
||||
is bound to at least one device, from the kernel's bind/unbind
|
||||
uevents, e.g.\&
|
||||
.Cm driver/mt7530 .
|
||||
.Sh D-BUS INTERFACE
|
||||
With D-Bus support,
|
||||
.Nm
|
||||
serves
|
||||
.Cm org.finit.Device1
|
||||
at
|
||||
.Pa /org/finit/device
|
||||
on its own brokerless bus,
|
||||
.Pa unix:path=/run/keventd/bus ,
|
||||
the same way
|
||||
.Xr finit 8
|
||||
serves
|
||||
.Cm org.finit .
|
||||
Methods:
|
||||
.Bl -tag -width RulesReload -offset indent -compact
|
||||
.It Cm Settle (u) -> b
|
||||
Wait until the device event queue drains, timeout in seconds
|
||||
.It Cm Trigger (ss)
|
||||
Replay events: action and subsystem glob, empty glob matches all
|
||||
.It Cm Info (s) -> a{ss}
|
||||
Device properties for a devpath, from /run/udev/data
|
||||
.It Cm RulesReload () -> u
|
||||
Re-read the udev rules directories, returns the rule count
|
||||
.El
|
||||
.Pp
|
||||
Properties
|
||||
.Cm QueueEmpty (b)
|
||||
and
|
||||
.Cm SeqnumProcessed (t)
|
||||
expose the queue state; the
|
||||
.Cm DeviceProcessed (ss)
|
||||
signal fires after each fully handled event.
|
||||
.Sh NETLINK REBROADCAST
|
||||
The Linux kernel sends uevents to netlink multicast group 1 (bit 0) of
|
||||
.Dv NETLINK_KOBJECT_UEVENT .
|
||||
Only the device manager listens on this raw kernel group. Userspace
|
||||
consumers such as applications using
|
||||
.Sy libudev
|
||||
expect to receive processed events on a separate netlink group.
|
||||
.Pp
|
||||
.Sy systemd/udevd
|
||||
established the convention of rebroadcasting to a separate group, and
|
||||
.Sy libudev-zero ,
|
||||
a daemonless replacement for libudev, listens on group 0x4 for these
|
||||
events. Without rebroadcast, graphical applications, Wayland and X11
|
||||
compositors, libinput, and other libudev consumers will not receive
|
||||
device hotplug events.
|
||||
.Pp
|
||||
.Nm
|
||||
rebroadcasts by default to netlink group 4 (bit 2, i.e.\&
|
||||
.Li 0x4 ) .
|
||||
A second netlink socket is created at startup, and after each uevent
|
||||
has been fully processed (device nodes created, modules loaded,
|
||||
etc.\&), the original event is sent to the configured group. This
|
||||
ensures that device nodes and symlinks already exist by the time
|
||||
consumers receive the event.
|
||||
.Pp
|
||||
Use
|
||||
.Fl g
|
||||
to override the default group mask, or
|
||||
.Fl G
|
||||
to disable rebroadcast entirely.
|
||||
.Pp
|
||||
See also:
|
||||
.Lk https://github.com/illiliti/libudev-zero "libudev-zero" ,
|
||||
.Lk https://skarnet.org/software/mdevd/ "mdevd" .
|
||||
.Sh SIGNALS
|
||||
.Bl -tag -width SIGUSR1
|
||||
.It Dv SIGHUP
|
||||
Re-read the udev rules directories.
|
||||
.It Dv SIGUSR1
|
||||
Toggle debug logging at runtime.
|
||||
.It Dv SIGTERM
|
||||
Graceful shutdown.
|
||||
.El
|
||||
.Sh FILES
|
||||
.Bl -tag -width /run/finit/cond/dev/ -compact
|
||||
.It Pa /dev/
|
||||
Device nodes managed by
|
||||
.Nm .
|
||||
.It Pa /dev/disk/by-id/ , Pa /dev/disk/by-path/
|
||||
Persistent block device symlinks.
|
||||
.It Pa /dev/input/by-id/ , Pa /dev/input/by-path/
|
||||
Persistent input device symlinks.
|
||||
.It Pa /lib/firmware/
|
||||
Firmware search path.
|
||||
.It Pa /run/finit/cond/dev/
|
||||
Device condition symlinks, also
|
||||
.Pa class/
|
||||
and
|
||||
.Pa driver/ .
|
||||
.It Pa /run/finit/cond/sys/pwr/
|
||||
Power supply condition symlinks.
|
||||
.It Pa /lib/udev/rules.d/ , Pa /run/udev/rules.d/ , Pa /etc/udev/rules.d/
|
||||
udev rules, searched in this order.
|
||||
.It Pa /run/udev/data/
|
||||
Per-device property database, libudev compatible.
|
||||
.It Pa /run/keventd/bus
|
||||
The
|
||||
.Cm org.finit.Device1
|
||||
D-Bus socket.
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr finit 8 ,
|
||||
.Xr finit.conf 5 ,
|
||||
.Xr initctl 8 ,
|
||||
.Xr mknod 2 ,
|
||||
.Xr modprobe 8
|
||||
.Sh AUTHORS
|
||||
.An Joachim Wiberg Aq Mt troglobit@gmail.com
|
||||
@@ -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
|
||||
|
||||
+2
-9
@@ -13,9 +13,6 @@ pkglibexec_PROGRAMS = getty logit runparts tmpfiles
|
||||
if SULOGIN
|
||||
pkglibexec_PROGRAMS += sulogin
|
||||
endif
|
||||
if KEVENTD
|
||||
pkglibexec_PROGRAMS += keventd
|
||||
endif
|
||||
if WATCHDOGD
|
||||
pkglibexec_PROGRAMS += watchdogd
|
||||
endif
|
||||
@@ -30,11 +27,6 @@ getty_CFLAGS = -W -Wall -Wextra -std=gnu99
|
||||
getty_CFLAGS += $(lite_CFLAGS)
|
||||
getty_LDADD = $(lite_LIBS)
|
||||
|
||||
keventd_SOURCES = keventd.c iwatch.c iwatch.h util.c util.h
|
||||
keventd_CFLAGS = -W -Wall -Wextra -std=gnu99
|
||||
keventd_CFLAGS += $(lite_CFLAGS)
|
||||
keventd_LDADD = $(lite_LIBS)
|
||||
|
||||
runparts_SOURCES = runparts.c
|
||||
runparts_CFLAGS = -W -Wall -Wextra -std=gnu99
|
||||
runparts_CFLAGS += $(lite_CFLAGS)
|
||||
@@ -105,11 +97,12 @@ endif
|
||||
initctl_SOURCES = initctl.c initctl.h cgutil.c cgutil.h \
|
||||
client.c client.h cond.c cond.h reboot.c \
|
||||
serv.c serv.h svc.h util.c util.h log.h
|
||||
initctl_CPPFLAGS = $(AM_CPPFLAGS)
|
||||
initctl_CFLAGS = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99
|
||||
initctl_CFLAGS += $(lite_CFLAGS) $(uev_CFLAGS)
|
||||
initctl_LDADD = $(lite_LIBS) $(uev_LIBS)
|
||||
if DBUS
|
||||
initctl_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/libink
|
||||
initctl_CPPFLAGS += -I$(top_srcdir)/libink
|
||||
initctl_LDADD += $(top_builddir)/libink/libink.la
|
||||
endif
|
||||
|
||||
|
||||
@@ -61,53 +61,19 @@ static int call(int (*action)(svc_t *, void *), char *buf, size_t len)
|
||||
static int stop(svc_t *svc, void *user_data)
|
||||
{
|
||||
(void)user_data;
|
||||
|
||||
if (!svc)
|
||||
return 1;
|
||||
|
||||
service_timeout_cancel(svc);
|
||||
svc_stop(svc);
|
||||
service_step(svc);
|
||||
if (!IS_RESERVED_RUNLEVEL(runlevel))
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
return 0;
|
||||
return service_stop_now(svc);
|
||||
}
|
||||
|
||||
static int start(svc_t *svc, void *user_data)
|
||||
{
|
||||
(void)user_data;
|
||||
|
||||
if (!svc)
|
||||
return 1;
|
||||
|
||||
service_timeout_cancel(svc);
|
||||
svc_start(svc);
|
||||
service_step(svc);
|
||||
if (!IS_RESERVED_RUNLEVEL(runlevel))
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
return 0;
|
||||
return service_start_now(svc);
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: this does not wait for svc to be stopped first, that is the
|
||||
* responsibility of initctl to do. Otherwise we'd block PID 1,
|
||||
* or introduce some nasty race conditions.
|
||||
*/
|
||||
static int restart(svc_t *svc, void *user_data)
|
||||
{
|
||||
if (!svc)
|
||||
return 1;
|
||||
|
||||
if (!svc_is_running(svc))
|
||||
return start(svc, user_data);
|
||||
|
||||
service_timeout_cancel(svc);
|
||||
service_stop(svc);
|
||||
service_step(svc);
|
||||
|
||||
return 0;
|
||||
(void)user_data;
|
||||
return service_restart_now(svc);
|
||||
}
|
||||
|
||||
static int reload(svc_t *svc, void *user_data)
|
||||
@@ -222,16 +188,7 @@ static svc_t *do_find_byc(char *buf, size_t len)
|
||||
return svc_find_by_cond(input);
|
||||
}
|
||||
|
||||
static void bypass_shutdown(void *);
|
||||
struct wq emergency = { .cb = bypass_shutdown };
|
||||
|
||||
static void bypass_shutdown(void *unused)
|
||||
{
|
||||
(void)unused;
|
||||
|
||||
cprintf("TIMEOUT TIMEOUT SHUTTING DOWN NOW!!\n");
|
||||
do_shutdown(halt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle switch_root API command.
|
||||
@@ -302,10 +259,7 @@ static int do_reboot(int cmd, int timeout, char *buf, size_t len)
|
||||
return 255;
|
||||
}
|
||||
|
||||
if (timeout > 0) {
|
||||
emergency.delay = timeout * 1000;
|
||||
schedule_work(&emergency);
|
||||
}
|
||||
shutdown_bypass(timeout);
|
||||
|
||||
switch (cmd) {
|
||||
case INIT_CMD_REBOOT:
|
||||
@@ -375,8 +329,8 @@ static void api_cb(uev_t *w, void *arg, int events)
|
||||
{
|
||||
static svc_t *iter = NULL;
|
||||
struct init_request rq;
|
||||
int sd, lvl;
|
||||
svc_t *svc;
|
||||
int sd;
|
||||
|
||||
(void)arg;
|
||||
|
||||
@@ -454,14 +408,6 @@ static void api_cb(uev_t *w, void *arg, int events)
|
||||
|
||||
switch (rq.cmd) {
|
||||
case INIT_CMD_RUNLVL:
|
||||
/* Allow changing cfglevel in runlevel S */
|
||||
if (IS_RESERVED_RUNLEVEL(runlevel)) {
|
||||
if (runlevel != INIT_LEVEL) {
|
||||
warnx("Cannot abort runlevel 6/0.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (rq.runlevel) {
|
||||
case 's':
|
||||
case 'S':
|
||||
@@ -470,17 +416,7 @@ static void api_cb(uev_t *w, void *arg, int events)
|
||||
|
||||
case '0'...'9':
|
||||
dbg("Setting new runlevel %c", rq.runlevel);
|
||||
lvl = rq.runlevel - '0';
|
||||
if (lvl == 0)
|
||||
halt = SHUT_OFF;
|
||||
if (lvl == 6)
|
||||
halt = SHUT_REBOOT;
|
||||
|
||||
/* User requested change in next runlevel */
|
||||
if (runlevel == INIT_LEVEL)
|
||||
cfglevel = lvl;
|
||||
else
|
||||
sm_runlevel(lvl);
|
||||
sm_request_runlevel(rq.runlevel - '0');
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -7,12 +7,16 @@
|
||||
|
||||
#define COND_BASE "finit/cond"
|
||||
#define COND_DEV "dev/"
|
||||
#define COND_DRIVER "driver/"
|
||||
#define COND_CLASS "class/"
|
||||
#define COND_PID "pid/"
|
||||
#define COND_SYS "sys/"
|
||||
#define COND_USR "usr/"
|
||||
|
||||
#define _PATH_COND _PATH_VARRUN COND_BASE "/"
|
||||
#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_CONDSYS _PATH_COND COND_SYS
|
||||
#define _PATH_CONDUSR _PATH_COND COND_USR
|
||||
|
||||
+31
-28
@@ -701,9 +701,9 @@ char *conf_escape(const char *str, char *buf, size_t len)
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by plugins and similar that dynamically generate system
|
||||
* services. @body holds the block contents, one indented line per
|
||||
* setting.
|
||||
* Generates /run/finit/system/<file> for Finit's built-in services,
|
||||
* which must probe the running system before registering. @fmt holds
|
||||
* the block contents, one indented line per setting.
|
||||
*/
|
||||
void conf_save_service(int type, const char *name, char *file, const char *fmt, ...)
|
||||
{
|
||||
@@ -2307,28 +2307,23 @@ int conf_reload(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* Next, read all *.conf in /lib/finit/system and /etc/finit.d/
|
||||
* The system files were previously created at runtime by plugins
|
||||
* but are now regular files that can be overridden by files in
|
||||
* /etc/finit.d -- similar to how tmfiles.d(5) work. E.g., add
|
||||
* an override .conf, or an ignore by symlinking to /dev/null
|
||||
* Next, read all *.conf in /run/finit/system, /lib/finit/system,
|
||||
* and /etc/finit.d/. The runtime directory holds files Finit
|
||||
* generates for its built-in services (keventd, watchdogd,
|
||||
* runparts), read first so later files can reference them in
|
||||
* if: statements. A same-named file in a later directory
|
||||
* overrides an earlier one, similar to how tmpfiles.d(5) work,
|
||||
* so /etc/finit.d, always read last, is how users override any
|
||||
* built-in or bundled configuration.
|
||||
*
|
||||
* The .conf files (and run/task/service stanzas) are parsed and
|
||||
* started in order. Each directory is sorted alphanumerically
|
||||
* and then the result is appended to the overall order:
|
||||
*
|
||||
* /lib/finit/system/10-hotplug.conf
|
||||
* /lib/finit/system/90-testserv.conf
|
||||
* /run/finit/system/dbus.conf
|
||||
* /run/finit/system/tty.conf
|
||||
* /etc/finit.d/10-abc.conf
|
||||
* /etc/finit.d/20-abc.conf
|
||||
* /etc/finit.d/enabled/1-aaa.conf
|
||||
* /etc/finit.d/enabled/1-abc.conf
|
||||
* /etc/finit.d/enabled/2-aaa.conf
|
||||
* The .conf files (and their run/task/service stanzas) are
|
||||
* parsed and started in order. Each directory is sorted
|
||||
* alphanumerically before being appended to the overall order,
|
||||
* recorded in /run/finit/conf.order at bootstrap. See
|
||||
* doc/config/files.md for an example.
|
||||
*/
|
||||
glob_append(&gl, 0, "%s/*.conf", FINIT_SYSPATH_);
|
||||
glob_append(&gl, 1, "%s/*.conf", FINIT_RUNPATH_);
|
||||
glob_append(&gl, 0, "%s/*.conf", FINIT_RUNPATH_);
|
||||
glob_append(&gl, 1, "%s/*.conf", FINIT_SYSPATH_);
|
||||
glob_append(&gl, 1, "%s/*.conf", finit_rcsd);
|
||||
glob_append(&gl, 1, "%s/enabled/*.conf", finit_rcsd);
|
||||
|
||||
@@ -2683,15 +2678,23 @@ int conf_init(uev_ctx_t *ctx)
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* Start kernel event daemon as soon as possible, if enabled
|
||||
* Start kernel event daemon as soon as possible, if enabled.
|
||||
* In passive mode (-p) alongside the hotplug plugin, keventd
|
||||
* only monitors power supply events (sys/pwr/ac condition).
|
||||
*/
|
||||
if (whichp(FINIT_EXECPATH_ "/keventd"))
|
||||
conf_save_service(SVC_TYPE_SERVICE, "keventd", "keventd.conf",
|
||||
"\tdescription = \"Finit kernel event daemon\"\n"
|
||||
"\trunlevel = \"S12345789\"\n"
|
||||
"\tnotify = \"none\"\n"
|
||||
"\tdescription = \"kernel event daemon\"\n"
|
||||
"\trunlevel = \"S0123456789\"\n"
|
||||
"\tnotify = \"pid\"\n"
|
||||
"\tcgroup init {}\n"
|
||||
"\tcommand = \"" FINIT_EXECPATH_ "/keventd\"\n");
|
||||
"\tcommand = \"" FINIT_EXECPATH_ "/keventd"
|
||||
#ifdef HAVE_HOTPLUG_PLUGIN
|
||||
" -p"
|
||||
#else
|
||||
" -c"
|
||||
#endif
|
||||
"\"\n");
|
||||
|
||||
dbg("Allow plugins to register early runlevel 1 run/task/services ...");
|
||||
plugin_run_hooks(HOOK_SVC_PLUGIN);
|
||||
|
||||
+71
-50
@@ -314,46 +314,24 @@ static int manager_get_service(link_call_t *call, void *userdata)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Service-control helpers used by Start/Stop/Restart/Reload. These
|
||||
* mirror the static helpers in api.c — kept private here so api.c
|
||||
* stays untouched in this increment. */
|
||||
/* svc_parse_jobstr-style adapters over the shared service helpers */
|
||||
|
||||
static int dbus_apply_stop(svc_t *svc, void *user_data)
|
||||
{
|
||||
(void)user_data;
|
||||
if (!svc)
|
||||
return 1;
|
||||
service_timeout_cancel(svc);
|
||||
svc_stop(svc);
|
||||
service_step(svc);
|
||||
if (!IS_RESERVED_RUNLEVEL(runlevel))
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
return 0;
|
||||
return service_stop_now(svc);
|
||||
}
|
||||
|
||||
static int dbus_apply_start(svc_t *svc, void *user_data)
|
||||
{
|
||||
(void)user_data;
|
||||
if (!svc)
|
||||
return 1;
|
||||
service_timeout_cancel(svc);
|
||||
svc_start(svc);
|
||||
service_step(svc);
|
||||
if (!IS_RESERVED_RUNLEVEL(runlevel))
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
return 0;
|
||||
return service_start_now(svc);
|
||||
}
|
||||
|
||||
static int dbus_apply_restart(svc_t *svc, void *user_data)
|
||||
{
|
||||
if (!svc)
|
||||
return 1;
|
||||
if (!svc_is_running(svc))
|
||||
return dbus_apply_start(svc, user_data);
|
||||
service_timeout_cancel(svc);
|
||||
service_stop(svc);
|
||||
service_step(svc);
|
||||
return 0;
|
||||
(void)user_data;
|
||||
return service_restart_now(svc);
|
||||
}
|
||||
|
||||
struct dispatch_ctx {
|
||||
@@ -454,9 +432,7 @@ static int manager_set_runlevel(link_call_t *call, void *userdata)
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"runlevel must be 0-9 (excluding internal levels)");
|
||||
|
||||
if (lvl == 0) halt = SHUT_OFF;
|
||||
if (lvl == 6) halt = SHUT_REBOOT;
|
||||
sm_runlevel((int)lvl);
|
||||
sm_request_runlevel((int)lvl);
|
||||
|
||||
(void)link_call_reply(call);
|
||||
return 0;
|
||||
@@ -464,11 +440,18 @@ static int manager_set_runlevel(link_call_t *call, void *userdata)
|
||||
|
||||
static int dbus_shutdown(link_call_t *call, shutop_t target, int level)
|
||||
{
|
||||
uint32_t timeout;
|
||||
|
||||
if (link_call_read_u32(call, &timeout) < 0)
|
||||
return link_call_reply_error(call,
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"expected (u)");
|
||||
if (IS_RESERVED_RUNLEVEL(runlevel))
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.WrongRunlevel",
|
||||
"Already in shutdown");
|
||||
halt = target;
|
||||
shutdown_bypass((int)timeout);
|
||||
sm_runlevel(level);
|
||||
(void)link_call_reply(call);
|
||||
return 0;
|
||||
@@ -490,11 +473,9 @@ static int signal_one(svc_t *svc, void *udata)
|
||||
{
|
||||
int signo = *(int *)udata;
|
||||
|
||||
/* Silently skip stopped services -- a multi-match ident
|
||||
* (e.g. "sshd:*") should not fail the whole call just because
|
||||
* one of the matches happens to be in a halted state. */
|
||||
/* Signalling a stopped service is an error, like the legacy API */
|
||||
if (!svc_is_running(svc))
|
||||
return 0;
|
||||
return 1;
|
||||
return !!kill(svc->pid, signo);
|
||||
}
|
||||
|
||||
@@ -535,6 +516,10 @@ static int manager_signal(link_call_t *call, void *u)
|
||||
static int manager_suspend(link_call_t *call, void *u)
|
||||
{
|
||||
(void)u;
|
||||
if (IS_RESERVED_RUNLEVEL(runlevel))
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.WrongRunlevel",
|
||||
"Unsupported command in runlevel S and 6/0");
|
||||
sync();
|
||||
if (suspend() < 0) {
|
||||
const char *msg = (errno == EINVAL)
|
||||
@@ -623,11 +608,11 @@ static const link_method_t manager_methods[] = {
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_reload },
|
||||
{ .name = "SetRunlevel", .in_sig = "u", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_set_runlevel },
|
||||
{ .name = "Reboot", .in_sig = "", .out_sig = "",
|
||||
{ .name = "Reboot", .in_sig = "u", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_reboot },
|
||||
{ .name = "Poweroff", .in_sig = "", .out_sig = "",
|
||||
{ .name = "Poweroff", .in_sig = "u", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_poweroff },
|
||||
{ .name = "Halt", .in_sig = "", .out_sig = "",
|
||||
{ .name = "Halt", .in_sig = "u", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_halt },
|
||||
{ .name = "Suspend", .in_sig = "", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_suspend },
|
||||
@@ -638,10 +623,18 @@ static const link_method_t manager_methods[] = {
|
||||
{ NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static const link_signal_t manager_signals[] = {
|
||||
{ .name = "ServiceStateChanged", .sig = "sss" },
|
||||
{ .name = "RunlevelChanged", .sig = "ss" },
|
||||
{ .name = "ConfigReloaded", .sig = "" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const link_vtable_t manager_vtable = {
|
||||
.interface = "org.finit.Manager1",
|
||||
.methods = manager_methods,
|
||||
.properties = manager_properties,
|
||||
.signals = manager_signals,
|
||||
};
|
||||
|
||||
/* ---------- org.finit.Service1 (one object per service) ----------
|
||||
@@ -665,7 +658,11 @@ static int service_action_method(link_call_t *call, void *userdata,
|
||||
"org.finit.Error.NoSuchService",
|
||||
"Service object no longer valid");
|
||||
|
||||
action(svc, NULL);
|
||||
if (action(svc, NULL))
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.Failed",
|
||||
"failed on service");
|
||||
|
||||
(void)link_call_reply(call);
|
||||
return 0;
|
||||
}
|
||||
@@ -674,18 +671,15 @@ static int service1_start (link_call_t *c, void *u) { return service_action_met
|
||||
static int service1_stop (link_call_t *c, void *u) { return service_action_method(c, u, dbus_apply_stop); }
|
||||
static int service1_restart(link_call_t *c, void *u) { return service_action_method(c, u, dbus_apply_restart); }
|
||||
|
||||
static int service1_reload(link_call_t *call, void *userdata)
|
||||
static int dbus_apply_reload(svc_t *svc, void *user_data)
|
||||
{
|
||||
svc_t *svc = userdata;
|
||||
(void)user_data;
|
||||
return service_reload(svc);
|
||||
}
|
||||
|
||||
if (!svc)
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.NoSuchService",
|
||||
"Service object no longer valid");
|
||||
|
||||
service_reload(svc);
|
||||
(void)link_call_reply(call);
|
||||
return 0;
|
||||
static int service1_reload(link_call_t *c, void *u)
|
||||
{
|
||||
return service_action_method(c, u, dbus_apply_reload);
|
||||
}
|
||||
|
||||
static const link_method_t service_methods[] = {
|
||||
@@ -1049,6 +1043,18 @@ void dbus_notify_runlevel_change(int old_level, int new_level)
|
||||
"RunlevelChanged", "ss", body, (size_t)blen);
|
||||
}
|
||||
|
||||
/*
|
||||
* Reconfiguration complete: all conditions have been re-asserted by
|
||||
* their in-Finit owners. External providers whose conditions are
|
||||
* generation files, rather than the oneshot symlinks keventd uses,
|
||||
* subscribe to this to re-assert theirs.
|
||||
*/
|
||||
void dbus_notify_reload(void)
|
||||
{
|
||||
dbus_emit_signal("/org/finit/manager", "org.finit.Manager1",
|
||||
"ConfigReloaded", "", NULL, 0);
|
||||
}
|
||||
|
||||
/* ---------- org.finit.Cond1 ---------- */
|
||||
|
||||
#define COND_PATH_OBJECT "/org/finit/cond"
|
||||
@@ -1148,7 +1154,7 @@ static int cond1_set_or_clear(link_call_t *call, int do_set)
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"Set/Clear is restricted to usr/* conditions");
|
||||
|
||||
if (do_set)
|
||||
if (do_set) {
|
||||
/* cond_set_oneshot, not cond_set: a user-asserted condition
|
||||
* is a symlink to _PATH_RECONF, so it tracks the reconf
|
||||
* generation automatically and stays "on" across reloads
|
||||
@@ -1157,8 +1163,17 @@ static int cond1_set_or_clear(link_call_t *call, int do_set)
|
||||
* semantics for user conditions, and what initctl cond set
|
||||
* has done forever via the filesystem path. */
|
||||
cond_set_oneshot(full);
|
||||
else
|
||||
if (cond_get(full) != COND_ON)
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.Failed",
|
||||
"failed asserting condition");
|
||||
} else {
|
||||
cond_clear(full);
|
||||
if (cond_get(full) != COND_OFF)
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.Failed",
|
||||
"failed clearing condition");
|
||||
}
|
||||
|
||||
(void)link_call_reply(call);
|
||||
return 0;
|
||||
@@ -1256,9 +1271,15 @@ static const link_method_t cond_methods[] = {
|
||||
{ NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static const link_signal_t cond_signals[] = {
|
||||
{ .name = "ConditionChanged", .sig = "ss" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const link_vtable_t cond_vtable = {
|
||||
.interface = COND_INTERFACE,
|
||||
.methods = cond_methods,
|
||||
.signals = cond_signals,
|
||||
};
|
||||
|
||||
/* ---------- signal emission: ConditionChanged ---------- */
|
||||
|
||||
+52
-13
@@ -58,6 +58,30 @@ static struct dev_node *find_node(const char *cond)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int devmon_cond(const char *cond)
|
||||
{
|
||||
return !strncmp(cond, COND_DEV, strlen(COND_DEV)) ||
|
||||
!strncmp(cond, COND_CLASS, strlen(COND_CLASS)) ||
|
||||
!strncmp(cond, COND_DRIVER, strlen(COND_DRIVER));
|
||||
}
|
||||
|
||||
/*
|
||||
* keventd asserts class/ and driver/ conditions for devices that never
|
||||
* get a /dev node, e.g. LEDs and DSA switch ports. For those the
|
||||
* condition file is the only record of the device.
|
||||
*/
|
||||
static int dev_exists(const char *cond)
|
||||
{
|
||||
char path[PATH_MAX], tmp[PATH_MAX];
|
||||
|
||||
snprintf(path, sizeof(path), "/%s", cond);
|
||||
if (fexist(path))
|
||||
return 1;
|
||||
|
||||
snprintf(tmp, sizeof(tmp), _PATH_COND "%s", cond);
|
||||
return fexist(pid_runpath(tmp, path, sizeof(path)));
|
||||
}
|
||||
|
||||
static void drop_node(struct dev_node *node)
|
||||
{
|
||||
if (!node)
|
||||
@@ -75,9 +99,8 @@ static void drop_node(struct dev_node *node)
|
||||
void devmon_add_cond(const char *cond)
|
||||
{
|
||||
struct dev_node *node;
|
||||
char path[PATH_MAX];
|
||||
|
||||
if (!cond || strncmp(cond, "dev/", 4)) {
|
||||
if (!cond || !devmon_cond(cond)) {
|
||||
// dbg("no match %s", cond ?: "<NIL>");
|
||||
return;
|
||||
}
|
||||
@@ -105,14 +128,13 @@ void devmon_add_cond(const char *cond)
|
||||
|
||||
TAILQ_INSERT_TAIL(&dev_node_list, node, link);
|
||||
|
||||
snprintf(path, sizeof(path), "/%s", cond);
|
||||
if (fexist(path))
|
||||
if (dev_exists(cond))
|
||||
cond_set(cond);
|
||||
}
|
||||
|
||||
void devmon_del_cond(const char *cond)
|
||||
{
|
||||
if (!cond || strncmp(cond, COND_DEV, strlen(COND_DEV)))
|
||||
if (!cond || !devmon_cond(cond))
|
||||
return;
|
||||
|
||||
drop_node(find_node(cond));
|
||||
@@ -121,11 +143,9 @@ void devmon_del_cond(const char *cond)
|
||||
void devmon_reconf(void)
|
||||
{
|
||||
struct dev_node *node, *tmp;
|
||||
char path[PATH_MAX];
|
||||
|
||||
TAILQ_FOREACH_SAFE(node, &dev_node_list, link, tmp) {
|
||||
snprintf(path, sizeof(path), "/%s", node->name);
|
||||
if (fexist(path))
|
||||
if (dev_exists(node->name))
|
||||
cond_set(node->name);
|
||||
else
|
||||
cond_clear(node->name);
|
||||
@@ -161,6 +181,18 @@ static void devmon_update_conds(char *dir, char *name, uint32_t mask)
|
||||
char *cond;
|
||||
|
||||
paste(fn, sizeof(fn), dir, name);
|
||||
|
||||
/*
|
||||
* Events in the condition tree come from keventd, the file is
|
||||
* the condition state so only kick affected services, like the
|
||||
* sys/usr plugins do.
|
||||
*/
|
||||
cond = strstr(fn, COND_BASE);
|
||||
if (cond) {
|
||||
cond_update(cond + strlen(COND_BASE) + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
cond = &fn[1];
|
||||
|
||||
// dbg("path: %s, mask: %08x, cond: %s", fn, mask, cond);
|
||||
@@ -273,23 +305,30 @@ static void devmon_cb(uev_t *w, void *arg, int events)
|
||||
|
||||
void devmon_init(uev_ctx_t *ctx)
|
||||
{
|
||||
const char *cond_dirs[] = { _PATH_CONDDEV, _PATH_CONDCLASS, _PATH_CONDDRIVER };
|
||||
char dir[MAX_ARG_LEN];
|
||||
size_t i;
|
||||
|
||||
fd = iwatch_init(&iw_devmon);
|
||||
if (fd < 0)
|
||||
return;
|
||||
|
||||
if (mkpath(pid_runpath(_PATH_CONDDEV, dir, sizeof(dir)), 0755) && errno != EEXIST) {
|
||||
err(1, "Failed creating %s condition directory, %s", COND_DEV, _PATH_CONDDEV);
|
||||
return;
|
||||
}
|
||||
|
||||
if (uev_io_init(ctx, &devw, devmon_cb, NULL, fd, UEV_READ)) {
|
||||
err(1, "Failed setting up I/O callback for /dev watcher");
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < NELEMS(cond_dirs); i++) {
|
||||
pid_runpath(cond_dirs[i], dir, sizeof(dir));
|
||||
if (mkpath(dir, 0755) && errno != EEXIST) {
|
||||
err(1, "Failed creating condition directory %s", dir);
|
||||
continue;
|
||||
}
|
||||
|
||||
devmon_add_path(&iw_devmon, dir);
|
||||
}
|
||||
|
||||
strlcpy(dir, _PATH_DEV, sizeof(dir));
|
||||
if (devmon_add_path(&iw_devmon, dir))
|
||||
iwatch_exit(&iw_devmon);
|
||||
|
||||
+16
-3
@@ -1067,13 +1067,26 @@ static int do_cmd(int cmd)
|
||||
#ifdef HAVE_DBUS
|
||||
static int do_reboot_dbus(const char *method)
|
||||
{
|
||||
int rc = try_dbus_manager(method, "", NULL);
|
||||
link_client_t *c;
|
||||
int rc;
|
||||
|
||||
if (rc == 0) {
|
||||
c = link_client_open(FINIT_BUS_SOCKET);
|
||||
if (!c)
|
||||
return -1; /* fall back to legacy transport */
|
||||
|
||||
/* Timeout in seconds arms the emergency shutdown bypass, 0 = none */
|
||||
rc = link_client_call_v(c, "/org/finit/manager",
|
||||
"org.finit.Manager1", method,
|
||||
"u", (uint32_t)timeout);
|
||||
if (rc == LINK_CALL_ERROR)
|
||||
map_dbus_err(c, method, NULL); /* exits */
|
||||
link_client_close(c);
|
||||
|
||||
if (rc == LINK_CALL_OK) {
|
||||
sleep(5); /* match legacy: wait for finit to shut down */
|
||||
return 0;
|
||||
}
|
||||
return rc; /* 1 = error, -1 = fall back */
|
||||
return -1; /* fall back */
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
-319
@@ -1,319 +0,0 @@
|
||||
/* Listens to kernel events like AC power status and manages sys/ conditions
|
||||
*
|
||||
* Copyright (c) 2021-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 <dirent.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/netlink.h>
|
||||
|
||||
#ifdef _LIBITE_LITE
|
||||
# include <libite/lite.h>
|
||||
#else
|
||||
# include <lite/lite.h>
|
||||
#endif
|
||||
|
||||
#include "cond.h"
|
||||
#include "pid.h"
|
||||
#include "util.h"
|
||||
|
||||
#define _PATH_SYSFS_PWR "/sys/class/power_supply"
|
||||
|
||||
static int num_ac_online;
|
||||
static int num_ac;
|
||||
|
||||
static int running = 1;
|
||||
static int level;
|
||||
static int logon;
|
||||
|
||||
int debug; /* debug in other modules as well */
|
||||
|
||||
void logit(int prio, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
if (logon)
|
||||
vsyslog(prio, fmt, ap);
|
||||
else if (prio <= level) {
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
#define panic(fmt, args...) { logit(LOG_CRIT, fmt ":%s", ##args, strerror(errno)); exit(1); }
|
||||
#define warn(fmt, args...) { logit(LOG_WARNING, fmt ":%s", ##args, strerror(errno)); }
|
||||
|
||||
static void sys_cond(char *cond, int set)
|
||||
{
|
||||
char oneshot[256];
|
||||
|
||||
snprintf(oneshot, sizeof(oneshot), "%s/%s", _PATH_CONDSYS, cond);
|
||||
if (set) {
|
||||
if (symlink(_PATH_RECONF, oneshot) && errno != EEXIST)
|
||||
warn("failed asserting sys/%s", cond);
|
||||
} else {
|
||||
if (erase(oneshot) && errno != ENOENT)
|
||||
warn("failed asserting sys/%s", cond);
|
||||
}
|
||||
}
|
||||
|
||||
static int fgetline(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;
|
||||
}
|
||||
|
||||
chomp(buf);
|
||||
fclose(fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_online(char *online)
|
||||
{
|
||||
int val;
|
||||
|
||||
if (!online)
|
||||
return 0;
|
||||
|
||||
val = atoi(online);
|
||||
logit(LOG_INFO, "AC %s", val ? "connected" : "disconnected");
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int is_ac(char *type)
|
||||
{
|
||||
char *types[] = {
|
||||
"Mains",
|
||||
"USB",
|
||||
"BrickID",
|
||||
"Wireless",
|
||||
NULL
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; types[i]; i++) {
|
||||
if (!strncmp(type, types[i], strlen(types[i])))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void init(void)
|
||||
{
|
||||
struct dirent **d = NULL;
|
||||
char *cond_dirs[] = {
|
||||
_PATH_CONDSYS,
|
||||
_PATH_CONDSYS "/pwr",
|
||||
};
|
||||
char path[384];
|
||||
int i, n;
|
||||
|
||||
for (i = 0; i < (int)NELEMS(cond_dirs); i++) {
|
||||
if (mkpath(cond_dirs[i], 0755) && errno != EEXIST) {
|
||||
warn("Failed creating %s condition directory, %s", COND_SYS,
|
||||
cond_dirs[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
n = scandir(_PATH_SYSFS_PWR, &d, NULL, alphasort);
|
||||
for (i = 0; i < n; i++) {
|
||||
char *nm = d[i]->d_name;
|
||||
char buf[10];
|
||||
|
||||
snprintf(path, sizeof(path), "%s/%s/type", _PATH_SYSFS_PWR, nm);
|
||||
if (!fgetline(path, buf, sizeof(buf)) && is_ac(buf)) {
|
||||
num_ac++;
|
||||
|
||||
snprintf(path, sizeof(path), "%s/%s/online", _PATH_SYSFS_PWR, nm);
|
||||
if (!fgetline(path, buf, sizeof(buf))) {
|
||||
if (check_online(buf))
|
||||
num_ac_online++;
|
||||
}
|
||||
}
|
||||
free(d[i]);
|
||||
}
|
||||
|
||||
if (n > 0)
|
||||
free(d);
|
||||
|
||||
/* if any power_supply is online, or none can be found */
|
||||
if (num_ac == 0 || num_ac_online > 0)
|
||||
sys_cond("pwr/ac", 1);
|
||||
}
|
||||
|
||||
static void set_logging(int prio)
|
||||
{
|
||||
setlogmask(LOG_UPTO(prio));
|
||||
level = prio;
|
||||
}
|
||||
|
||||
static void toggle_debug(int signo)
|
||||
{
|
||||
(void)signo;
|
||||
|
||||
debug ^= 1;
|
||||
set_logging(debug ? LOG_DEBUG : LOG_NOTICE);
|
||||
}
|
||||
|
||||
static void shut_down(int signo)
|
||||
{
|
||||
(void)signo;
|
||||
running = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Started by Finit as soon as possible when base filesystem is up,
|
||||
* modules have been probed, or insmodded from /etc/finit.conf, so by
|
||||
* now we should have /sys/class/power_supply/ available for probing.
|
||||
* If none is found we assert /sys/pwr/ac condition anyway, this is what
|
||||
* systemd does (ConditionACPower) and also makes most sense.
|
||||
*/
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct sockaddr_nl nls = { 0 };
|
||||
struct pollfd pfd;
|
||||
char buf[1024];
|
||||
|
||||
if (argc > 1) {
|
||||
if (!strcmp(argv[1], "-d"))
|
||||
debug = 1;
|
||||
}
|
||||
|
||||
if (!debug) {
|
||||
openlog("keventd", LOG_PID, LOG_DAEMON);
|
||||
set_logging(LOG_NOTICE);
|
||||
logon = 1;
|
||||
}
|
||||
|
||||
signal(SIGUSR1, toggle_debug);
|
||||
signal(SIGTERM, shut_down);
|
||||
init();
|
||||
|
||||
pfd.events = POLLIN;
|
||||
pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
|
||||
if (pfd.fd == -1)
|
||||
panic("failed creating netlink socket");
|
||||
|
||||
nls.nl_family = AF_NETLINK;
|
||||
nls.nl_pid = 0;
|
||||
nls.nl_groups = -1;
|
||||
if (bind(pfd.fd, (void *)&nls, sizeof(struct sockaddr_nl)))
|
||||
panic("bind failed");
|
||||
|
||||
logit(LOG_DEBUG, "Waiting for events ...");
|
||||
while (running) {
|
||||
char *path = NULL;
|
||||
int ac = 0;
|
||||
int i, len;
|
||||
|
||||
if (-1 == poll(&pfd, 1, -1)) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
len = recv(pfd.fd, buf, sizeof(buf), MSG_DONTWAIT);
|
||||
if (len == -1) {
|
||||
switch (errno) {
|
||||
case EINTR:
|
||||
continue;
|
||||
case ENOBUFS:
|
||||
warn("lost events");
|
||||
continue;
|
||||
default:
|
||||
panic("unhandled");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
buf[len] = 0;
|
||||
logit(LOG_DEBUG, "%s", buf);
|
||||
|
||||
/* skip libusb events, focus on kernel events/changes */
|
||||
if (strncmp(buf, "change@", 7))
|
||||
continue;
|
||||
|
||||
/* XXX: currently limited to monitoring this subsystem */
|
||||
if (!strstr(buf, "power_supply"))
|
||||
continue;
|
||||
|
||||
i = 0;
|
||||
while (i < len) {
|
||||
char *line = buf + i;
|
||||
|
||||
logit(LOG_DEBUG, "%s", line);
|
||||
if (!strncmp(line, "DEVPATH=", 8)) {
|
||||
path = &line[8];
|
||||
logit(LOG_DEBUG, "Got path %s", path);
|
||||
} else if (!strncmp(line, "POWER_SUPPLY_TYPE=", 18)) {
|
||||
ac = is_ac(&line[18]);
|
||||
} else if (!strncmp(line, "POWER_SUPPLY_ONLINE=", 20) && ac) {
|
||||
if (check_online(&line[20])) {
|
||||
if (!num_ac_online)
|
||||
sys_cond("pwr/ac", 1);
|
||||
num_ac_online++;
|
||||
} else {
|
||||
if (num_ac_online > 0)
|
||||
num_ac_online--;
|
||||
if (!num_ac_online)
|
||||
sys_cond("pwr/ac", 0);
|
||||
}
|
||||
}
|
||||
|
||||
i += strlen(line) + 1;
|
||||
}
|
||||
}
|
||||
close(pfd.fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
@@ -54,6 +54,7 @@ void dbus_unregister_service (svc_t *svc);
|
||||
void dbus_notify_service_state (svc_t *svc, int old_state, int new_state);
|
||||
void dbus_notify_condition_change(const char *name, const char *state);
|
||||
void dbus_notify_runlevel_change(int old_level, int new_level);
|
||||
void dbus_notify_reload (void);
|
||||
#endif
|
||||
void conf_flush_events(void);
|
||||
|
||||
|
||||
+13
-1
@@ -193,7 +193,19 @@ int run_parts(char *dir, char *cmd, const char *env[], int progress, int sysv)
|
||||
#ifndef __FINIT__
|
||||
static int usage(int rc)
|
||||
{
|
||||
warnx("usage: runparts [-bdhps?] DIRECTORY");
|
||||
fprintf(stderr,
|
||||
"Usage: runparts [OPTIONS] DIRECTORY\n"
|
||||
"\n"
|
||||
"Run all executable scripts in DIRECTORY in alphabetical order.\n"
|
||||
"Scripts prefixed S<NUM> are called with 'start', K<NUM> with 'stop'.\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -b Batch mode, disable progress output and ANSI colors\n"
|
||||
" -d Enable debug output\n"
|
||||
" -h This help text\n"
|
||||
" -p Show progress and exit status for each script\n"
|
||||
" -s SysV compat, only run S<NUM>name and K<NUM>name scripts\n"
|
||||
"\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
@@ -1442,6 +1442,77 @@ int service_stop(svc_t *svc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* service_stop_now - Stop a service on user request
|
||||
* @svc: Service to stop
|
||||
*
|
||||
* Shared by the legacy API and the D-Bus methods, like
|
||||
* service_reload().
|
||||
*
|
||||
* Returns:
|
||||
* POSIX OK(0) on success, non-zero if @svc is NULL.
|
||||
*/
|
||||
int service_stop_now(svc_t *svc)
|
||||
{
|
||||
if (!svc)
|
||||
return 1;
|
||||
|
||||
service_timeout_cancel(svc);
|
||||
svc_stop(svc);
|
||||
service_step(svc);
|
||||
if (!IS_RESERVED_RUNLEVEL(runlevel))
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* service_start_now - Start a service on user request
|
||||
* @svc: Service to start
|
||||
*
|
||||
* Returns:
|
||||
* POSIX OK(0) on success, non-zero if @svc is NULL.
|
||||
*/
|
||||
int service_start_now(svc_t *svc)
|
||||
{
|
||||
if (!svc)
|
||||
return 1;
|
||||
|
||||
service_timeout_cancel(svc);
|
||||
svc_start(svc);
|
||||
service_step(svc);
|
||||
if (!IS_RESERVED_RUNLEVEL(runlevel))
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* service_restart_now - Restart a service on user request
|
||||
* @svc: Service to restart
|
||||
*
|
||||
* Does not wait for @svc to stop first, that is the caller's
|
||||
* (initctl) responsibility. Otherwise we'd block PID 1, or
|
||||
* introduce some nasty race conditions.
|
||||
*
|
||||
* Returns:
|
||||
* POSIX OK(0) on success, non-zero if @svc is NULL.
|
||||
*/
|
||||
int service_restart_now(svc_t *svc)
|
||||
{
|
||||
if (!svc)
|
||||
return 1;
|
||||
|
||||
if (!svc_is_running(svc))
|
||||
return service_start_now(svc);
|
||||
|
||||
service_timeout_cancel(svc);
|
||||
service_stop(svc);
|
||||
service_step(svc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* service_reload - Request reload of a service, driven by the state machine
|
||||
* @svc: Service to reload
|
||||
|
||||
@@ -60,6 +60,9 @@ void service_forked (svc_t *svc);
|
||||
void service_ready (svc_t *svc, int ready);
|
||||
|
||||
int service_stop (svc_t *svc);
|
||||
int service_start_now (svc_t *svc);
|
||||
int service_stop_now (svc_t *svc);
|
||||
int service_restart_now (svc_t *svc);
|
||||
int service_reload (svc_t *svc);
|
||||
int service_step (svc_t *svc);
|
||||
void service_step_all (int types);
|
||||
|
||||
@@ -94,6 +94,53 @@ static void shutdown_wdt_cb(uev_t *w, void *arg, int events)
|
||||
do_shutdown(halt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Emergency shutdown bypass, same job as the shutdown watchdog above
|
||||
* but armed by the user: `initctl -t SEC reboot`, on either transport.
|
||||
*/
|
||||
static void bypass_shutdown(void *unused)
|
||||
{
|
||||
(void)unused;
|
||||
|
||||
cprintf("TIMEOUT TIMEOUT SHUTTING DOWN NOW!!\n");
|
||||
do_shutdown(halt);
|
||||
}
|
||||
|
||||
static struct wq emergency = { .cb = bypass_shutdown };
|
||||
|
||||
void shutdown_bypass(int timeout)
|
||||
{
|
||||
if (timeout <= 0)
|
||||
return;
|
||||
|
||||
emergency.delay = timeout * 1000;
|
||||
schedule_work(&emergency);
|
||||
}
|
||||
|
||||
/*
|
||||
* User-requested runlevel change, shared by the legacy API and the
|
||||
* D-Bus SetRunlevel method. Refused in runlevel 0/6, deferred via
|
||||
* cfglevel during bootstrap.
|
||||
*/
|
||||
void sm_request_runlevel(int lvl)
|
||||
{
|
||||
if (IS_RESERVED_RUNLEVEL(runlevel) && runlevel != INIT_LEVEL) {
|
||||
warnx("Cannot abort runlevel 6/0.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (lvl == 0)
|
||||
halt = SHUT_OFF;
|
||||
if (lvl == 6)
|
||||
halt = SHUT_REBOOT;
|
||||
|
||||
/* Runlevel S: user requested change in next runlevel */
|
||||
if (runlevel == INIT_LEVEL)
|
||||
cfglevel = lvl;
|
||||
else
|
||||
sm_runlevel(lvl);
|
||||
}
|
||||
|
||||
/*
|
||||
* Console input callback - handles Ctrl-C during bootstrap wait
|
||||
*/
|
||||
@@ -586,6 +633,9 @@ restart:
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
dbg("Reconfiguration done");
|
||||
#ifdef HAVE_DBUS
|
||||
dbus_notify_reload();
|
||||
#endif
|
||||
sm.state = SM_RUNNING_STATE;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ void sm_step (void);
|
||||
int sm_in_reload (void);
|
||||
void sm_reload (void);
|
||||
void sm_runlevel (int newlevel);
|
||||
void sm_request_runlevel(int lvl);
|
||||
void shutdown_bypass(int timeout);
|
||||
|
||||
#endif /* FINIT_SM_H_ */
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ EXTRA_DIST += failing-sysv.sh
|
||||
EXTRA_DIST += svc-env.sh
|
||||
EXTRA_DIST += global-envs.sh
|
||||
EXTRA_DIST += initctl-status-subset.sh
|
||||
EXTRA_DIST += keventd.sh
|
||||
EXTRA_DIST += notify.sh
|
||||
EXTRA_DIST += pidfile.sh
|
||||
EXTRA_DIST += stale-pidfile.sh
|
||||
@@ -82,6 +83,7 @@ EXTRA_DIST += dbus-service.sh
|
||||
EXTRA_DIST += dbus-cond.sh
|
||||
EXTRA_DIST += dbus-initctl.sh
|
||||
EXTRA_DIST += dbus-introspect.sh
|
||||
EXTRA_DIST += dbus-device.sh
|
||||
|
||||
AM_TESTS_ENVIRONMENT = SYSROOT='$(abs_builddir)/sysroot/';
|
||||
AM_TESTS_ENVIRONMENT += export SYSROOT;
|
||||
@@ -112,6 +114,9 @@ TESTS += failing-sysv.sh
|
||||
TESTS += svc-env.sh
|
||||
TESTS += global-envs.sh
|
||||
TESTS += initctl-status-subset.sh
|
||||
if KEVENTD
|
||||
TESTS += keventd.sh
|
||||
endif
|
||||
TESTS += notify.sh
|
||||
TESTS += pidfile.sh
|
||||
TESTS += stale-pidfile.sh
|
||||
@@ -146,6 +151,9 @@ TESTS += dbus-service.sh
|
||||
TESTS += dbus-cond.sh
|
||||
TESTS += dbus-initctl.sh
|
||||
TESTS += dbus-introspect.sh
|
||||
if KEVENTD
|
||||
TESTS += dbus-device.sh
|
||||
endif
|
||||
TESTS += fuzz-msg-parse.sh
|
||||
# Needs the plugin to bring up the bus, not just the built-in one
|
||||
if BUILD_DBUS_PLUGIN
|
||||
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/bin/sh
|
||||
# keventd: org.finit.Device1 on /run/keventd/bus.
|
||||
#
|
||||
# Covers the device manager's own bus: introspection, queue-state
|
||||
# properties, Settle, Info by devpath, RulesReload, Trigger, and the
|
||||
# DeviceProcessed signal. Network interfaces are the one device class
|
||||
# the unprivileged sandbox can hotplug, see keventd.sh.
|
||||
|
||||
set -eu
|
||||
|
||||
TEST_DIR=$(dirname "$0")
|
||||
|
||||
test_teardown()
|
||||
{
|
||||
say "Running test teardown."
|
||||
|
||||
run "ip link del dummy0 2>/dev/null || true"
|
||||
run "rm -f $FINIT_CONF"
|
||||
}
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
. "$TEST_DIR/lib/setup.sh"
|
||||
# shellcheck source=/dev/null
|
||||
. "$TEST_DIR/lib/dbus-setup.sh"
|
||||
|
||||
DEVBUS=/run/keventd/bus
|
||||
|
||||
if ! texec initctl -p status keventd >/dev/null 2>&1; then
|
||||
skip "keventd not enabled in this build"
|
||||
fi
|
||||
if ! run "ip link add probe0 type dummy 2>/dev/null"; then
|
||||
skip "cannot create dummy interfaces in test namespace"
|
||||
fi
|
||||
run "ip link del probe0"
|
||||
|
||||
say "keventd readiness"
|
||||
retry 'assert_ready "keventd"' 50 0.2
|
||||
retry "texec test -S $DEVBUS"
|
||||
|
||||
say "Device1 introspects with methods, properties, and signal"
|
||||
xml=$(texec "$CLIENT" introspect "$DEVBUS" /org/finit/device)
|
||||
for m in Settle Trigger Info RulesReload; do
|
||||
case "$xml" in
|
||||
*"<method name=\"$m\">"*) assert "$m declared" 0 -eq 0 ;;
|
||||
*) fail "$m missing from Device1 XML" ;;
|
||||
esac
|
||||
done
|
||||
case "$xml" in
|
||||
*'<property name="QueueEmpty" type="b"'*'<signal name="DeviceProcessed">'*)
|
||||
assert "QueueEmpty property and DeviceProcessed signal declared" 0 -eq 0 ;;
|
||||
*) fail "property/signal declarations missing: $xml" ;;
|
||||
esac
|
||||
|
||||
say "Queue-state properties are readable"
|
||||
seq1=$(texec "$CLIENT" getprop "$DEVBUS" /org/finit/device \
|
||||
org.finit.Device1 SeqnumProcessed)
|
||||
assert "SeqnumProcessed is numeric (got: $seq1)" "$seq1" -ge 0
|
||||
qe=$(texec "$CLIENT" getprop "$DEVBUS" /org/finit/device \
|
||||
org.finit.Device1 QueueEmpty)
|
||||
case "$qe" in
|
||||
true|false) assert "QueueEmpty reads $qe" 0 -eq 0 ;;
|
||||
*) fail "Unexpected QueueEmpty value: $qe" ;;
|
||||
esac
|
||||
|
||||
say "Device1.Settle answers within its timeout"
|
||||
texec "$CLIENT" call-u "$DEVBUS" /org/finit/device \
|
||||
org.finit.Device1 Settle 15 >/dev/null \
|
||||
|| fail "Settle returned non-zero"
|
||||
assert "Settle completed" 0 -eq 0
|
||||
|
||||
say "Interface add advances SeqnumProcessed and Info answers"
|
||||
run "ip link add dummy0 type dummy"
|
||||
retry 'assert_cond "class/net/dummy0"'
|
||||
seq2=$(texec "$CLIENT" getprop "$DEVBUS" /org/finit/device \
|
||||
org.finit.Device1 SeqnumProcessed)
|
||||
assert "SeqnumProcessed advanced ($seq1 -> $seq2)" "$seq2" -gt "$seq1"
|
||||
|
||||
info=$(texec "$CLIENT" call-s "$DEVBUS" /org/finit/device \
|
||||
org.finit.Device1 Info /devices/virtual/net/dummy0 2>&1) \
|
||||
|| fail "Info failed: $info"
|
||||
assert "Info answered for dummy0" 0 -eq 0
|
||||
|
||||
say "keventd -S settles via the bus"
|
||||
run "/libexec/finit/keventd -S -t 15" || fail "keventd -S failed"
|
||||
assert "bus-first settle ok" 0 -eq 0
|
||||
|
||||
say "Trigger(add, net) re-emits events, DeviceProcessed observed"
|
||||
rm -f /tmp/dbus-dev-sig.out
|
||||
( texec "$CLIENT" monitor-signal "$DEVBUS" \
|
||||
"type='signal',interface='org.finit.Device1',member='DeviceProcessed'" \
|
||||
5000 > /tmp/dbus-dev-sig.out 2>&1 ) &
|
||||
mon_pid=$!
|
||||
sleep 0.5
|
||||
texec "$CLIENT" call-ss "$DEVBUS" /org/finit/device \
|
||||
org.finit.Device1 Trigger add net >/dev/null 2>&1 \
|
||||
|| fail "Trigger returned non-zero"
|
||||
set +e
|
||||
wait "$mon_pid"
|
||||
mon_rc=$?
|
||||
set -e
|
||||
assert "monitor saw DeviceProcessed (rc=$mon_rc)" "$mon_rc" -eq 0
|
||||
case "$(cat /tmp/dbus-dev-sig.out)" in
|
||||
*"DeviceProcessed"*net*) assert "Signal payload names a net device" 0 -eq 0 ;;
|
||||
*) fail "Unexpected signal output: $(cat /tmp/dbus-dev-sig.out)" ;;
|
||||
esac
|
||||
|
||||
say "RulesReload succeeds and Trigger rejects a bogus action"
|
||||
texec "$CLIENT" call-void "$DEVBUS" /org/finit/device \
|
||||
org.finit.Device1 RulesReload >/dev/null \
|
||||
|| fail "RulesReload failed"
|
||||
assert "RulesReload ok" 0 -eq 0
|
||||
|
||||
set +e
|
||||
texec "$CLIENT" call-ss "$DEVBUS" /org/finit/device \
|
||||
org.finit.Device1 Trigger frobnicate "" >/tmp/dbus-trg.out 2>&1
|
||||
trg_rc=$?
|
||||
set -e
|
||||
assert "Bogus action rejected (rc=$trg_rc)" "$trg_rc" -eq 1
|
||||
case "$(cat /tmp/dbus-trg.out)" in
|
||||
*InvalidArgs*) assert "Error is InvalidArgs" 0 -eq 0 ;;
|
||||
*) fail "Unexpected reply: $(cat /tmp/dbus-trg.out)" ;;
|
||||
esac
|
||||
|
||||
say "Device1.RulesReload from non-root is rejected with AccessDenied"
|
||||
texec chmod 0666 "$DEVBUS"
|
||||
set +e
|
||||
texec "$CLIENT" call-void-as-uid 1 "$DEVBUS" /org/finit/device \
|
||||
org.finit.Device1 RulesReload >/tmp/dbus-devauthz.out 2>&1
|
||||
dauthz_rc=$?
|
||||
set -e
|
||||
assert "Non-root RulesReload rejected (rc=$dauthz_rc)" "$dauthz_rc" -eq 1
|
||||
case "$(cat /tmp/dbus-devauthz.out)" in
|
||||
*AccessDenied*) assert "Device1 authz fires" 0 -eq 0 ;;
|
||||
*) fail "Unexpected reply: $(cat /tmp/dbus-devauthz.out)" ;;
|
||||
esac
|
||||
|
||||
say "Conditions are generation symlinks, unaffected by initctl reload"
|
||||
run "initctl reload"
|
||||
retry 'assert_cond "class/net/dummy0"'
|
||||
assert "class/net/dummy0 still asserted after reload" 0 -eq 0
|
||||
@@ -41,6 +41,13 @@ texec initctl reload >/dev/null \
|
||||
|| fail "initctl reload returned non-zero"
|
||||
assert "initctl reload ok" 0 -eq 0
|
||||
|
||||
# keventd was restarted above and only writes its pidfile once the
|
||||
# coldplug queue has drained, so <pid/keventd> asserts a good while
|
||||
# after the restart returns. Let it land before subscribing below,
|
||||
# or the monitor catches keventd's ConditionChanged instead of ours
|
||||
# and stops, since it waits for exactly one signal.
|
||||
retry 'assert_cond pid/keventd' 100 0.1
|
||||
|
||||
# A ConditionChanged signal can only originate from Cond1.Set going
|
||||
# through finit (the legacy filesystem path doesn't emit signals).
|
||||
# So if the monitor sees one, we know initctl cond set was routed
|
||||
|
||||
@@ -29,3 +29,36 @@ case "$xml" in
|
||||
fail "per-character <arg> fragment leaked" ;;
|
||||
*) assert "no bracket/array fragments" 0 -eq 0 ;;
|
||||
esac
|
||||
|
||||
# ---------- <signal> declarations ----------
|
||||
|
||||
say "Cond1 declares the ConditionChanged signal"
|
||||
case "$xml" in
|
||||
*'<signal name="ConditionChanged">'*) assert "ConditionChanged declared" 0 -eq 0 ;;
|
||||
*) fail "ConditionChanged missing from Cond1 XML" ;;
|
||||
esac
|
||||
|
||||
say "Manager1 declares ServiceStateChanged and RunlevelChanged"
|
||||
mgr=$(texec "$CLIENT" introspect "$BUS" /org/finit/manager)
|
||||
case "$mgr" in
|
||||
*'<signal name="ServiceStateChanged">'*'<signal name="RunlevelChanged">'*)
|
||||
assert "Manager1 signals declared" 0 -eq 0 ;;
|
||||
*) fail "Manager1 signal declarations missing" ;;
|
||||
esac
|
||||
|
||||
say "Properties declares PropertiesChanged, org.freedesktop.DBus is present"
|
||||
case "$mgr" in
|
||||
*'<signal name="PropertiesChanged">'*) assert "PropertiesChanged declared" 0 -eq 0 ;;
|
||||
*) fail "PropertiesChanged missing from Properties XML" ;;
|
||||
esac
|
||||
# Hello/AddMatch/RemoveMatch are answered on the canonical object
|
||||
# only, so they must be declared there and nowhere else.
|
||||
drv=$(texec "$CLIENT" introspect "$BUS" /org/freedesktop/DBus)
|
||||
case "$drv" in
|
||||
*'<interface name="org.freedesktop.DBus">'*) assert "org.freedesktop.DBus declared on canonical object" 0 -eq 0 ;;
|
||||
*) fail "org.freedesktop.DBus missing from /org/freedesktop/DBus" ;;
|
||||
esac
|
||||
case "$mgr" in
|
||||
*'<interface name="org.freedesktop.DBus">'*) fail "org.freedesktop.DBus leaked onto /org/finit/manager" ;;
|
||||
*) assert "org.freedesktop.DBus not advertised off-path" 0 -eq 0 ;;
|
||||
esac
|
||||
@@ -137,3 +137,49 @@ case "$rl" in
|
||||
*)
|
||||
fail "Unexpected initctl runlevel output: $rl" ;;
|
||||
esac
|
||||
|
||||
# ---------- legacy-parity edge semantics ----------
|
||||
|
||||
say "Manager1.SetRunlevel with bogus level returns InvalidArgs"
|
||||
set +e
|
||||
texec "$CLIENT" call-u "$BUS" /org/finit/manager \
|
||||
org.finit.Manager1 SetRunlevel 42 >/tmp/dbus-rl.out 2>&1
|
||||
rl_rc=$?
|
||||
set -e
|
||||
assert "Bogus runlevel rejected (rc=$rl_rc)" "$rl_rc" -eq 1
|
||||
case "$(cat /tmp/dbus-rl.out)" in
|
||||
*InvalidArgs*) assert "Error is InvalidArgs" 0 -eq 0 ;;
|
||||
*) fail "Unexpected reply: $(cat /tmp/dbus-rl.out)" ;;
|
||||
esac
|
||||
|
||||
say "Manager1.Signal on a stopped service returns Failed"
|
||||
cat >> "$SYSROOT$FINIT_CONF" <<CONF
|
||||
service stopped-serv {
|
||||
manual = true
|
||||
command = "serv -np"
|
||||
}
|
||||
CONF
|
||||
run "initctl reload"
|
||||
set +e
|
||||
texec "$CLIENT" call-su "$BUS" /org/finit/manager \
|
||||
org.finit.Manager1 Signal stopped-serv 1 >/tmp/dbus-sig.out 2>&1
|
||||
sig_rc=$?
|
||||
set -e
|
||||
assert "Signal to stopped service rejected (rc=$sig_rc)" "$sig_rc" -eq 1
|
||||
case "$(cat /tmp/dbus-sig.out)" in
|
||||
*Failed*) assert "Error is Failed" 0 -eq 0 ;;
|
||||
*) fail "Unexpected reply: $(cat /tmp/dbus-sig.out)" ;;
|
||||
esac
|
||||
|
||||
# Reboot cannot be invoked without shutting down the sandbox, so
|
||||
# verify the timeout argument via introspection instead.
|
||||
say "Reboot family declares the timeout argument in introspection"
|
||||
flat=$(printf '%s' "$xml" | tr -d '\n')
|
||||
for m in Reboot Poweroff Halt; do
|
||||
case "$flat" in
|
||||
*"<method name=\"$m\"> <arg type=\"u\" direction=\"in\"/>"*)
|
||||
assert "$m takes (u)" 0 -eq 0 ;;
|
||||
*)
|
||||
fail "$m does not declare the timeout argument" ;;
|
||||
esac
|
||||
done
|
||||
@@ -94,3 +94,25 @@ case "$(cat /tmp/dbus-sig.out)" in
|
||||
*)
|
||||
fail "Unexpected signal output: $(cat /tmp/dbus-sig.out)" ;;
|
||||
esac
|
||||
|
||||
# ---------- name:id identities and _HH path encoding ----------
|
||||
|
||||
say "Manager1.GetService(dhcp-client:eth1) hex-escapes the object path"
|
||||
cat >> "$SYSROOT$FINIT_CONF" <<CONF
|
||||
service dhcp-client:eth1 {
|
||||
manual = true
|
||||
command = "serv -np"
|
||||
}
|
||||
CONF
|
||||
run "initctl reload"
|
||||
epath=$(texec "$CLIENT" get-service "$BUS" "dhcp-client:eth1")
|
||||
assert "Encoded path (got: $epath)" "$epath" = "/org/finit/service/dhcp_2dclient_3aeth1"
|
||||
|
||||
say "The encoded service object introspects and answers Properties.Get"
|
||||
exml=$(texec "$CLIENT" introspect "$BUS" "$epath")
|
||||
case "$exml" in
|
||||
*'org.finit.Service1'*) assert "Service1 on encoded path" 0 -eq 0 ;;
|
||||
*) fail "Service1 missing on $epath" ;;
|
||||
esac
|
||||
eident=$(texec "$CLIENT" getprop "$BUS" "$epath" org.finit.Service1 Identity)
|
||||
assert "Identity round-trips (got: $eident)" "$eident" = "dhcp-client:eth1"
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Verify keventd, the bundled device manager. Network interfaces are
|
||||
# the only devices an unprivileged test can hotplug: the sandbox has
|
||||
# its own network namespace, so `ip link add` makes the kernel emit
|
||||
# genuine uevents for keventd to process.
|
||||
#
|
||||
# Verifies that keventd signals readiness, that an interface add event
|
||||
# asserts <class/net/IFNAME> to start a dependent service -- but never
|
||||
# <dev/IFNAME>, interfaces are not device nodes -- that the
|
||||
# /run/udev/data entry uses libudev-style n<ifindex> keying, and that
|
||||
# the remove event reverses it all.
|
||||
#
|
||||
|
||||
set -eu
|
||||
|
||||
TEST_DIR=$(dirname "$0")
|
||||
|
||||
test_teardown()
|
||||
{
|
||||
say "Running test teardown."
|
||||
|
||||
run "ip link del dummy0 2>/dev/null || true"
|
||||
run "rm -f $FINIT_CONF"
|
||||
}
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
. "$TEST_DIR/lib/setup.sh"
|
||||
|
||||
if ! texec initctl -p status keventd >/dev/null 2>&1; then
|
||||
skip "keventd not enabled in this build"
|
||||
fi
|
||||
|
||||
if ! run "ip link add probe0 type dummy 2>/dev/null"; then
|
||||
skip "cannot create dummy interfaces in test namespace"
|
||||
fi
|
||||
run "ip link del probe0"
|
||||
|
||||
sep "keventd readiness"
|
||||
retry 'assert_ready "keventd"' 50 0.2
|
||||
|
||||
sep "interface add asserts <class/net/dummy0>"
|
||||
cat >> "$SYSROOT$FINIT_CONF" <<EOF
|
||||
service serv {
|
||||
log { file = "/dev/null" }
|
||||
conditions = { "class/net/dummy0" }
|
||||
command = "serv -np"
|
||||
}
|
||||
EOF
|
||||
run "cat $FINIT_CONF"
|
||||
run "initctl reload"
|
||||
assert_status "serv" "waiting"
|
||||
|
||||
run "ip link add dummy0 type dummy"
|
||||
retry 'assert_cond "class/net/dummy0"'
|
||||
assert_nocond "dev/dummy0"
|
||||
retry 'assert_status "serv" "running"'
|
||||
|
||||
sep "udev database uses n<ifindex> keying"
|
||||
ifindex=$(texec cat /sys/class/net/dummy0/ifindex)
|
||||
assert_file_exists "/run/udev/data/n$ifindex"
|
||||
assert_file_contains "/run/udev/data/n$ifindex" "E:INTERFACE=dummy0"
|
||||
|
||||
sep "conditions survive initctl reload"
|
||||
run "initctl reload"
|
||||
retry 'assert_cond "class/net/dummy0"'
|
||||
retry 'assert_status "serv" "running"'
|
||||
|
||||
sep "interface remove clears <class/net/dummy0>"
|
||||
run "ip link del dummy0"
|
||||
retry 'assert_nocond "class/net/dummy0"'
|
||||
retry 'assert_status "serv" "waiting"'
|
||||
retry "texec test ! -e /run/udev/data/n$ifindex"
|
||||
+10
-8
@@ -43,15 +43,17 @@ _libs_nss := $(firstword $(wildcard /lib/$(ARCH)-linux-gnu/libnss_files.so.
|
||||
# one test can check Finit against dbus-daemon instead of only against
|
||||
# libink's own client. Absent is fine, dbus-broker.sh skips.
|
||||
dbus_bins := $(foreach b,dbus-daemon dbus-send,$(firstword $(wildcard /usr/bin/$(b) /bin/$(b))))
|
||||
# Given several binaries ldd prefixes each with a 'path:' header, and
|
||||
# that trailing colon would land in a make target. Excluding it here
|
||||
# is enough, no need for one ldd per binary.
|
||||
_libs_dbus := $(if $(dbus_bins),$(shell ldd $(dbus_bins) | grep -Eo '/[^ :]+'))
|
||||
|
||||
# The binaries stage exactly like the libraries: same host path, same
|
||||
# path under DEST, copied by the rule below.
|
||||
_libs_src := $(shell ldd $(FINITBIN) | grep -Eo '/[^ ]+') $(_libs_nss) \
|
||||
$(_libs_dbus) $(dbus_bins)
|
||||
# Bundled helpers like keventd link more libraries than finit itself,
|
||||
# and so does dbus-daemon. One ldd per binary: given several at once
|
||||
# ldd prefixes each with a 'path:' header, and that trailing colon
|
||||
# would land in a make target.
|
||||
_bins := $(FINITBIN) $(dbus_bins) \
|
||||
$(shell find $(abspath $(DEST)) -path '*libexec/finit*' -type f -perm -u+x 2>/dev/null)
|
||||
# The dbus binaries stage exactly like the libraries: same host path,
|
||||
# same path under DEST, copied by the rule below.
|
||||
_libs_src := $(foreach bin,$(_bins),$(shell ldd $(bin) 2>/dev/null | grep -Eo '/[^ ]+')) \
|
||||
$(_libs_nss) $(dbus_bins)
|
||||
libs := $(foreach path,$(sort $(_libs_src)),$(abspath $(DEST))$(path))
|
||||
|
||||
all: $(libs) $(DEST)/bin/$(BBBIN)
|
||||
|
||||
@@ -30,6 +30,13 @@ fi
|
||||
timeout=50
|
||||
while [ "$pid" -le 1 ]; do
|
||||
sleep 0.1
|
||||
# Once Finit gives up on a service it forks the post:script and
|
||||
# reports that PID as the service's, so a slay still waiting for
|
||||
# the service to come back would kill the script instead.
|
||||
st=$(initctl -p status "$nm" 2>/dev/null | awk '/Status/{print $3}')
|
||||
if [ "$st" = "crashed" ]; then
|
||||
exit 1
|
||||
fi
|
||||
pid=$(getpid "$nm")
|
||||
if [ "$pid" -le 1 ]; then
|
||||
timeout=$((timeout - 1))
|
||||
|
||||
@@ -322,6 +322,58 @@ static int mode_call_s(int argc, char *argv[])
|
||||
return do_call(argv[2], argv[3], argv[4], argv[5], argv[6]);
|
||||
}
|
||||
|
||||
/* "u" and "su" variants for methods taking a uint32 argument */
|
||||
static int do_call_u(const char *sock, const char *obj, const char *iface,
|
||||
const char *method, const char *arg, uint32_t val)
|
||||
{
|
||||
link_client_t *c;
|
||||
int rc;
|
||||
|
||||
c = link_client_open(sock);
|
||||
if (!c) return 2;
|
||||
|
||||
rc = arg
|
||||
? link_client_call_v(c, obj, iface, method, "su", arg, val)
|
||||
: link_client_call_v(c, obj, iface, method, "u", val);
|
||||
rc = report_rc(c, rc);
|
||||
if (rc == 0)
|
||||
printf("OK\n");
|
||||
link_client_close(c);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mode_call_ss(int argc, char *argv[])
|
||||
{
|
||||
link_client_t *c;
|
||||
int rc;
|
||||
|
||||
if (argc != 8) return 2;
|
||||
c = link_client_open(argv[2]);
|
||||
if (!c) return 2;
|
||||
|
||||
rc = link_client_call_v(c, argv[3], argv[4], argv[5],
|
||||
"ss", argv[6], argv[7]);
|
||||
rc = report_rc(c, rc);
|
||||
if (rc == 0)
|
||||
printf("OK\n");
|
||||
link_client_close(c);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mode_call_u(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 7) return 2;
|
||||
return do_call_u(argv[2], argv[3], argv[4], argv[5], NULL,
|
||||
(uint32_t)strtoul(argv[6], NULL, 0));
|
||||
}
|
||||
|
||||
static int mode_call_su(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 8) return 2;
|
||||
return do_call_u(argv[2], argv[3], argv[4], argv[5], argv[6],
|
||||
(uint32_t)strtoul(argv[7], NULL, 0));
|
||||
}
|
||||
|
||||
static int mode_call_void(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 6) return 2;
|
||||
@@ -375,7 +427,7 @@ static int mode_get_service(int argc, char *argv[])
|
||||
|
||||
/*
|
||||
* getprop BUS PATH IFACE PROP -- Properties.Get, prints the variant
|
||||
* value; dispatches on the wire signature, "s" and "u" supported.
|
||||
* value; dispatches on the wire signature: s, u, b, and t.
|
||||
*/
|
||||
static int mode_getprop(int argc, char *argv[])
|
||||
{
|
||||
@@ -405,6 +457,21 @@ static int mode_getprop(int argc, char *argv[])
|
||||
printf("%s\n", s);
|
||||
else if (type == 'u' && link_r_u32(&reader, &u) == 0)
|
||||
printf("%u\n", u);
|
||||
else if (type == 'b') {
|
||||
int b;
|
||||
|
||||
if (link_r_bool(&reader, &b) == 0)
|
||||
printf("%s\n", b ? "true" : "false");
|
||||
else
|
||||
rc = 2;
|
||||
} else if (type == 't') {
|
||||
uint64_t t;
|
||||
|
||||
if (link_r_u64(&reader, &t) == 0)
|
||||
printf("%llu\n", (unsigned long long)t);
|
||||
else
|
||||
rc = 2;
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "unsupported variant type '%c'\n", type);
|
||||
rc = 2;
|
||||
@@ -513,6 +580,9 @@ int main(int argc, char *argv[])
|
||||
if (!strcmp(argv[1], "introspect")) return mode_introspect (argc, argv);
|
||||
if (!strcmp(argv[1], "liststrings")) return mode_liststrings (argc, argv);
|
||||
if (!strcmp(argv[1], "call-s")) return mode_call_s (argc, argv);
|
||||
if (!strcmp(argv[1], "call-ss")) return mode_call_ss (argc, argv);
|
||||
if (!strcmp(argv[1], "call-u")) return mode_call_u (argc, argv);
|
||||
if (!strcmp(argv[1], "call-su")) return mode_call_su (argc, argv);
|
||||
if (!strcmp(argv[1], "call-void")) return mode_call_void (argc, argv);
|
||||
if (!strcmp(argv[1], "call-s-as-uid")) return mode_call_s_as_uid (argc, argv);
|
||||
if (!strcmp(argv[1], "call-void-as-uid")) return mode_call_void_as_uid(argc, argv);
|
||||
|
||||
Reference in new issue
Block a user