Files
finit/keventd/keventd.c
T
Joachim Wiberg 292e87c0d1 keventd: serve org.finit.Device1 on /run/keventd/bus
The udev parity gaps that were blocked on IPC -- settle, trigger,
info, queue introspection, runtime rule reload -- become bus methods.
keventd serves its own socket the way Finit serves /run/finit/bus:
brokerless, libink, one socket per daemon, no forwarding between the
two.

  Settle(u) -> b      parked until the queue drains or the timeout
                      passes; true when settled
  Trigger(s, s)       replay events, action + subsystem glob
  Info(s) -> a{ss}    /run/udev/data properties for a devpath
  RulesReload() -> u  re-read rules dirs, returns rule count
  QueueEmpty (b), SeqnumProcessed (t) properties
  DeviceProcessed (ss) signal after each fully handled event

The queue state is the highest kernel seqnum keventd has handled,
baselined at startup, against /sys/kernel/uevent_seqnum.  keventd -S
now asks the running daemon first and falls back to seqnum polling.
In passive mode Trigger and RulesReload refuse.  Adds
link_call_connection() for the park bookkeeping.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:43 +02:00

910 lines
22 KiB
C

/* 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 "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;
struct timespec last_change;
int primed;
};
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 void sys_cond(const 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(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;
}
chomp(buf);
fclose(fp);
return 0;
}
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)
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;
}
}
/*
* 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 *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++) {
const 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 init_dev_condition_dir(void)
{
char dir[256];
/* Create /run/finit/cond/dev/ directory for device conditions */
snprintf(dir, sizeof(dir), "%s", _PATH_CONDDEV);
if (mkpath(dir, 0755) && errno != EEXIST)
warn("Failed creating dev condition directory %s", dir);
}
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".
*/
/*
* 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;
}
unsigned long long kev_seq_processed(void)
{
return seq_processed;
}
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;
unsigned long long cur = 0;
struct timespec now;
long dt_ms;
(void)events;
if (read_uevent_seqnum(&cur))
return;
clock_gettime(CLOCK_MONOTONIC, &now);
if (!cg->primed) {
cg->last_seq = cur;
cg->last_change = now;
cg->primed = 1;
return;
}
if (cur != cg->last_seq) {
cg->last_seq = cur;
cg->last_change = now;
return;
}
dt_ms = (now.tv_sec - cg->last_change.tv_sec) * 1000 +
(now.tv_nsec - cg->last_change.tv_nsec) / 1000000;
if (dt_ms < 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)
{
unsigned long long last_seq = 0, cur_seq = 0;
struct timespec start, last_change, now;
clock_gettime(CLOCK_MONOTONIC, &start);
last_change = start;
while (1) {
if (read_uevent_seqnum(&cur_seq)) {
fprintf(stderr, "keventd: cannot read uevent_seqnum: %s\n",
strerror(errno));
return 1;
}
clock_gettime(CLOCK_MONOTONIC, &now);
if (cur_seq != last_seq) {
last_seq = cur_seq;
last_change = now;
} else {
long dt_ms = (now.tv_sec - last_change.tv_sec) * 1000 +
(now.tv_nsec - last_change.tv_nsec) / 1000000;
if (dt_ms >= stable_ms)
return 0;
}
if (now.tv_sec - start.tv_sec >= timeout_s)
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();
init_dev_condition_dir();
/* 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:
*/