keventd: fix hwdb parsing issues

This commit is contained in:
Basil Zubko committed 2026-10-31 20:42:07 +01:00
1 parent a220e0fa9b
commit 439cab0558
1 file changed
+76 -21
+76 -21
View File
@@ -81,6 +81,13 @@ static int builtin_kmod(struct uevent *ev, int argc, char **argv)
/* ---- hwdb -------------------------------------------------------- */ /* ---- hwdb -------------------------------------------------------- */
/*
* Longest lookup key. A key built by the stock name-based evdev rule
* (name, phys, ev and the DMI modalias) is 280 to 300 bytes with typical
* DMI strings, and 250 to 265 bytes for the plain name rule.
*/
#define HWDB_KEY_MAX 1024
/* /*
* Match a single text hwdb file against the lookup key. * Match a single text hwdb file against the lookup key.
* Text format: match lines at column 0, property lines indented. * Text format: match lines at column 0, property lines indented.
@@ -106,7 +113,11 @@ static void hwdb_match_file(const char *path, const char *lookup,
while (fgets(line, sizeof(line), fp)) { while (fgets(line, sizeof(line), fp)) {
line[strcspn(line, "\n")] = '\0'; line[strcspn(line, "\n")] = '\0';
if (!line[0] || line[0] == '#') { /* Comments do not end the record being read */
if (line[0] == '#')
continue;
if (!line[0]) {
matched = 0; matched = 0;
in_props = 0; in_props = 0;
continue; continue;
@@ -145,9 +156,11 @@ static void hwdb_match_file(const char *path, const char *lookup,
/* /*
* Build the hwdb lookup key for input devices. * Build the hwdb lookup key for input devices.
* *
* udev hwdb files use the format: * The key is the modalias of the input device, which the rule's
* evdev:input:b{BUS}v{VENDOR}p{PRODUCT}e{VERSION}* * --lookup-prefix (evdev:, id-input:modalias:) is put in front of:
* where all fields are 4-digit uppercase hex. * input:b{BUS}v{VENDOR}p{PRODUCT}e{VERSION}-e{CAPABILITIES...}
* where the IDs are 4-digit uppercase hex. If the device has no modalias
* attribute, the part up to the version is built from the id/ files.
* *
* The id/ files live under the input device directory. For eventN nodes * The id/ files live under the input device directory. For eventN nodes
* (devpath ends in .../inputN/eventN) we strip the last component first. * (devpath ends in .../inputN/eventN) we strip the last component first.
@@ -156,7 +169,7 @@ static void hwdb_input_key(const struct uevent *ev, char *key, size_t len)
{ {
char base[PATH_MAX]; char base[PATH_MAX];
char path[PATH_MAX]; char path[PATH_MAX];
char bus_s[8], vendor_s[8], product_s[8], version_s[8]; char bus_s[8] = "", vendor_s[8] = "", product_s[8] = "", version_s[8] = "";
if (!ev->devpath) if (!ev->devpath)
return; return;
@@ -180,11 +193,28 @@ static void hwdb_input_key(const struct uevent *ev, char *key, size_t len)
snprintf(path, sizeof(path), "/sys%s", ev->devpath); snprintf(path, sizeof(path), "/sys%s", ev->devpath);
} }
snprintf(base, sizeof(base), "%s/id/vendor", path); sysfs_read_file(base, vendor_s, sizeof(vendor_s)); /*
snprintf(base, sizeof(base), "%s/id/product", path); sysfs_read_file(base, product_s, sizeof(product_s)); * Prefer the modalias of the input device. Unlike the id/ files it
snprintf(base, sizeof(base), "%s/id/version", path); sysfs_read_file(base, version_s, sizeof(version_s)); * carries the capability bitmaps (-e0,1,2,4,k110,...) that some
* entries match on.
*/
snprintf(base, sizeof(base), "%s/modalias", path);
if (!sysfs_read_file(base, key, len) && !strncmp(key, "input:", 6))
return;
key[0] = '\0';
snprintf(key, len, "evdev:input:b%04Xv%04Xp%04Xe%04X*", /* Otherwise build it from the id/ files; without a full set there is no key. */
snprintf(base, sizeof(base), "%s/id/vendor", path);
if (sysfs_read_file(base, vendor_s, sizeof(vendor_s)) < 0)
return;
snprintf(base, sizeof(base), "%s/id/product", path);
if (sysfs_read_file(base, product_s, sizeof(product_s)) < 0)
return;
snprintf(base, sizeof(base), "%s/id/version", path);
if (sysfs_read_file(base, version_s, sizeof(version_s)) < 0)
return;
snprintf(key, len, "input:b%04Xv%04Xp%04Xe%04X*",
(unsigned)strtoul(bus_s, NULL, 16), (unsigned)strtoul(bus_s, NULL, 16),
(unsigned)strtoul(vendor_s, NULL, 16), (unsigned)strtoul(vendor_s, NULL, 16),
(unsigned)strtoul(product_s, NULL, 16), (unsigned)strtoul(product_s, NULL, 16),
@@ -265,24 +295,48 @@ static void hwdb_scan(const char *key, struct uevent *ev)
} }
} }
/* Insert prefix in front of key, in place; -1 if the result does not fit. */
static int hwdb_prepend(char *key, size_t len, const char *prefix)
{
size_t plen = strlen(prefix), klen = strlen(key);
if (plen + klen >= len)
return -1;
memmove(key + plen, key, klen + 1);
memcpy(key, prefix, plen);
return 0;
}
static int builtin_hwdb(struct uevent *ev, int argc, char **argv) static int builtin_hwdb(struct uevent *ev, int argc, char **argv)
{ {
const char *subsys = NULL; const char *subsys = NULL;
const char *prefix = NULL; const char *prefix = NULL;
char key[256] = ""; const char *explicit_key = NULL;
char key[HWDB_KEY_MAX] = "";
int i; int i;
/* Parse --subsystem=X and --lookup-prefix=Y */ /*
* Parse --subsystem=X and --lookup-prefix=Y. The first argument
* that is not an option is the lookup key itself, which is how the
* stock udev rules use the builtin:
*
* IMPORT{builtin}="hwdb 'evdev:name:$attr{name}:$attr{[dmi/id]modalias}'"
*/
for (i = 1; i < argc; i++) { for (i = 1; i < argc; i++) {
if (!strncmp(argv[i], "--subsystem=", 12)) if (!strncmp(argv[i], "--subsystem=", 12))
subsys = argv[i] + 12; subsys = argv[i] + 12;
else if (!strncmp(argv[i], "--lookup-prefix=", 16)) else if (!strncmp(argv[i], "--lookup-prefix=", 16))
prefix = argv[i] + 16; prefix = argv[i] + 16;
else if (argv[i][0] != '-' && !explicit_key)
explicit_key = argv[i];
} }
if (!subsys) if (!subsys)
subsys = ev->subsystem; subsys = ev->subsystem;
if (subsys && !strcmp(subsys, "input")) { if (explicit_key) {
if (snprintf(key, sizeof(key), "%s", explicit_key) >= (int)sizeof(key))
goto too_long;
} else if (subsys && !strcmp(subsys, "input")) {
hwdb_input_key(ev, key, sizeof(key)); hwdb_input_key(ev, key, sizeof(key));
} else if (subsys && (!strcmp(subsys, "usb") || } else if (subsys && (!strcmp(subsys, "usb") ||
!strcmp(subsys, "usb_device"))) { !strcmp(subsys, "usb_device"))) {
@@ -290,29 +344,30 @@ static int builtin_hwdb(struct uevent *ev, int argc, char **argv)
} }
/* Fall back to MODALIAS / ev->modalias for pci, platform, etc. */ /* Fall back to MODALIAS / ev->modalias for pci, platform, etc. */
if (!key[0]) { if (!key[0] && !explicit_key) {
const char *m = uevent_getenv(ev, "MODALIAS"); const char *m = uevent_getenv(ev, "MODALIAS");
if (!m) if (!m)
m = ev->modalias; m = ev->modalias;
if (m) if (m && snprintf(key, sizeof(key), "%s", m) >= (int)sizeof(key))
snprintf(key, sizeof(key), "%s", m); goto too_long;
} }
if (!key[0]) if (!key[0])
return 0; return 0;
/* --lookup-prefix scopes the match by prepending to the key. */ /* --lookup-prefix scopes the match by prepending to the key. */
if (prefix && *prefix) { if (prefix && *prefix && hwdb_prepend(key, sizeof(key), prefix))
char tmp[sizeof(key)]; goto too_long;
snprintf(tmp, sizeof(tmp), "%s%s", prefix, key);
snprintf(key, sizeof(key), "%s", tmp);
}
logit(LOG_DEBUG, "rules: hwdb lookup: %s", key); logit(LOG_DEBUG, "rules: hwdb lookup: %s", key);
hwdb_scan(key, ev); hwdb_scan(key, ev);
return 0; return 0;
too_long:
logit(LOG_WARNING, "rules: hwdb key too long (max %d bytes), lookup skipped",
HWDB_KEY_MAX - 1);
return 0;
} }
/* ---- path_id ----------------------------------------------------- */ /* ---- path_id ----------------------------------------------------- */