keventd: skip futile fork for missing /lib/udev/<helper>

When a rule references an absolute helper path (e.g. /lib/udev/fido_id,
/lib/udev/scsi_id) that the system does not ship and we have no matching
builtin either, return 1 from try_builtin_fallback() so the caller does
not fork /bin/sh on a binary that's known to be missing.  Previously
each such uevent left a zombie 127 child for keventd's sigchld_cb to
reap -- harmless but noisy and wasteful at coldplug.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg committed 2026-08-16 22:03:33 +02:00
1 parent 9d5e646e46
commit e4d9193f06
1 file changed
+34 -15
+34 -15
View File
@@ -1132,19 +1132,28 @@ static gid_t resolve_gid(const char *s)
* *
* Stock udev rules invoke /lib/udev/<helper> (path_id, usb_id, blkid, ...) * Stock udev rules invoke /lib/udev/<helper> (path_id, usb_id, blkid, ...)
* via IMPORT{program}=. keventd ships compatible builtins for many of * via IMPORT{program}=. keventd ships compatible builtins for many of
* those. If the helper is absent on this system, fall back to the * those. Dispatch order:
* matching builtin so the rule still works. If the helper exists, run
* it as-is -- this lets users override our builtins by dropping a
* custom helper into /lib/udev/.
* *
* Returns 1 if the builtin handled the command, 0 if the caller should * 1. Absolute helper path that exists -> let caller fork+exec it
* proceed with the normal fork+exec path. * (lets users override builtins by dropping a custom helper)
* 2. Basename matches a known builtin -> run the builtin in-process
* 3. Absolute helper path that does NOT exist and has no matching
* builtin -> skip silently with a debug
* log; forking /bin/sh on a missing binary just yields a 127
* child for every event (e.g. /lib/udev/fido_id, /lib/udev/scsi_id
* on systems that don't ship them).
* 4. Anything else (relative command, shell snippet, ...) -> caller
* forks /bin/sh -c which resolves via PATH.
*
* Returns 1 if the case has been handled here (caller skips fork),
* 0 if the caller should proceed with the normal fork+exec path.
*/ */
static int try_builtin_fallback(const char *cmd, struct uevent *ev) static int try_builtin_fallback(const char *cmd, struct uevent *ev)
{ {
char first[PATH_MAX], rebuilt[PATH_MAX]; char first[PATH_MAX], rebuilt[PATH_MAX];
const char *space, *base; const char *space, *base;
size_t len; size_t len;
int helper_missing;
space = strchr(cmd, ' '); space = strchr(cmd, ' ');
len = space ? (size_t)(space - cmd) : strlen(cmd); len = space ? (size_t)(space - cmd) : strlen(cmd);
@@ -1157,20 +1166,30 @@ static int try_builtin_fallback(const char *cmd, struct uevent *ev)
if (access(first, X_OK) == 0) if (access(first, X_OK) == 0)
return 0; return 0;
helper_missing = (first[0] == '/'); /* absolute path that's not there */
base = strrchr(first, '/'); base = strrchr(first, '/');
base = base ? base + 1 : first; base = base ? base + 1 : first;
if (!builtin_has(base)) if (builtin_has(base)) {
return 0; if (space)
snprintf(rebuilt, sizeof(rebuilt), "%s%s", base, space);
else
snprintf(rebuilt, sizeof(rebuilt), "%s", base);
if (space) logit(LOG_DEBUG, "rules: %s not found, falling back to builtin %s",
snprintf(rebuilt, sizeof(rebuilt), "%s%s", base, space); first, base);
else builtin_run(ev, rebuilt);
snprintf(rebuilt, sizeof(rebuilt), "%s", base); return 1;
}
logit(LOG_DEBUG, "rules: %s not found, falling back to builtin %s", first, base); if (helper_missing) {
builtin_run(ev, rebuilt); logit(LOG_DEBUG, "rules: %s not found and no matching builtin, skipping",
return 1; first);
return 1;
}
return 0;
} }
static void import_from_program(const char *cmd, struct uevent *ev) static void import_from_program(const char *cmd, struct uevent *ev)