diff options
author | Kay Sievers <kay@vrfy.org> | 2012-10-22 18:23:08 +0200 |
---|---|---|
committer | Kay Sievers <kay@vrfy.org> | 2012-10-23 16:43:32 +0200 |
commit | 796b06c21b62d13c9021e2fbd9c58a5c6edb2764 (patch) | |
tree | f46dc94e7589364887b9bb91589010f41d29706d /src/udev/udev-builtin-hwdb.c | |
parent | 59bb9d9a14889bee001706a32a518fe0a5009048 (diff) |
udev: add hardware database support
Diffstat (limited to 'src/udev/udev-builtin-hwdb.c')
-rw-r--r-- | src/udev/udev-builtin-hwdb.c | 476 |
1 files changed, 302 insertions, 174 deletions
diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c index 0db21641b1..5ccd06df04 100644 --- a/src/udev/udev-builtin-hwdb.c +++ b/src/udev/udev-builtin-hwdb.c @@ -1,22 +1,22 @@ -/* - * usb-db, pci-db - lookup vendor/product database - * - * Copyright (C) 2009 Lennart Poettering <lennart@poettering.net> - * Copyright (C) 2011 Kay Sievers <kay.sievers@vrfy.org> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ +/*** + This file is part of systemd. + + Copyright 2012 Kay Sievers <kay.sievers@vrfy.org> + Copyright 2008 Alan Jenkins <alan.christopher.jenkins@googlemail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ #include <stdio.h> #include <errno.h> @@ -24,224 +24,352 @@ #include <inttypes.h> #include <ctype.h> #include <stdlib.h> +#include <fnmatch.h> +#include <getopt.h> +#include <sys/mman.h> #include "udev.h" +#include "udev-hwdb.h" -static int get_id_attr( - struct udev_device *parent, - const char *name, - uint16_t *value) { +struct linebuf { + char bytes[LINE_MAX]; + size_t size; + size_t len; +}; - const char *t; - unsigned u; +static void linebuf_init(struct linebuf *buf) { + buf->size = 0; + buf->len = 0; +} - if (!(t = udev_device_get_sysattr_value(parent, name))) { - fprintf(stderr, "%s lacks %s.\n", udev_device_get_syspath(parent), name); - return -1; - } +static const char *linebuf_get(struct linebuf *buf) { + if (buf->len + 1 >= sizeof(buf->bytes)) + return NULL; + buf->bytes[buf->len] = '\0'; + return buf->bytes; +} + +static bool linebuf_add(struct linebuf *buf, const char *s, size_t len) { + if (buf->len + len >= sizeof(buf->bytes)) + return false; + memcpy(buf->bytes + buf->len, s, len); + buf->len += len; + return true; +} - if (startswith(t, "0x")) - t += 2; +static bool linebuf_add_char(struct linebuf *buf, char c) +{ + if (buf->len + 1 >= sizeof(buf->bytes)) + return false; + buf->bytes[buf->len++] = c; + return true; +} - if (sscanf(t, "%04x", &u) != 1 || u > 0xFFFFU) { - fprintf(stderr, "Failed to parse %s on %s.\n", name, udev_device_get_syspath(parent)); - return -1; - } +static void linebuf_rem(struct linebuf *buf, size_t count) { + assert(buf->len >= count); + buf->len -= count; +} - *value = (uint16_t) u; - return 0; +static void linebuf_rem_char(struct linebuf *buf) { + linebuf_rem(buf, 1); } -static int get_vid_pid( - struct udev_device *parent, - const char *vendor_attr, - const char *product_attr, - uint16_t *vid, - uint16_t *pid) { +struct trie_f { + struct udev_device *dev; + bool test; + FILE *f; + uint64_t file_time_usec; + union { + struct trie_header_f *head; + const char *map; + }; + size_t map_size; +}; - if (get_id_attr(parent, vendor_attr, vid) < 0) - return -1; - else if (*vid <= 0) { - fprintf(stderr, "Invalid vendor id.\n"); - return -1; - } +static const struct trie_child_entry_f *trie_node_children(struct trie_f *trie, const struct trie_node_f *node) { + return (const struct trie_child_entry_f *)((const char *)node + le64toh(trie->head->node_size)); +} - if (get_id_attr(parent, product_attr, pid) < 0) - return -1; +static const struct trie_value_entry_f *trie_node_values(struct trie_f *trie, const struct trie_node_f *node) { + const char *base = (const char *)node; - return 0; + base += le64toh(trie->head->node_size); + base += node->children_count * le64toh(trie->head->child_entry_size); + return (const struct trie_value_entry_f *)base; } -static void rstrip(char *n) { - size_t i; +static const struct trie_node_f *trie_node_from_off(struct trie_f *trie, le64_t off) { + return (const struct trie_node_f *)(trie->map + le64toh(off)); +} - for (i = strlen(n); i > 0 && isspace(n[i-1]); i--) - n[i-1] = 0; +static const char *trie_string(struct trie_f *trie, le64_t off) { + return trie->map + le64toh(off); } -#define HEXCHARS "0123456789abcdefABCDEF" -#define WHITESPACE " \t\n\r" -static int lookup_vid_pid(const char *database, - uint16_t vid, uint16_t pid, - char **vendor, char **product) -{ +static int trie_children_cmp_f(const void *v1, const void *v2) { + const struct trie_child_entry_f *n1 = v1; + const struct trie_child_entry_f *n2 = v2; - FILE *f; - int ret = -1; - int found_vendor = 0; - char *line = NULL; + return n1->c - n2->c; +} - *vendor = *product = NULL; +static const struct trie_node_f *node_lookup_f(struct trie_f *trie, const struct trie_node_f *node, uint8_t c) { + struct trie_child_entry_f *child; + struct trie_child_entry_f search; - if (!(f = fopen(database, "rme"))) { - fprintf(stderr, "Failed to open database file '%s': %s\n", database, strerror(errno)); - return -1; - } + search.c = c; + child = bsearch(&search, trie_node_children(trie, node), node->children_count, + le64toh(trie->head->child_entry_size), trie_children_cmp_f); + if (child) + return trie_node_from_off(trie, child->child_off); + return NULL; +} - for (;;) { - size_t n; +static void trie_fnmatch_f(struct trie_f *trie, const struct trie_node_f *node, size_t p, + struct linebuf *buf, const char *search, + void (*cb)(struct trie_f *trie, const char *key, const char *value)) { + size_t len; + size_t i; + const char *prefix; - if (getline(&line, &n, f) < 0) - break; + prefix = trie_string(trie, node->prefix_off); + len = strlen(prefix + p); + linebuf_add(buf, prefix + p, len); - rstrip(line); + for (i = 0; i < node->children_count; i++) { + const struct trie_child_entry_f *child = &trie_node_children(trie, node)[i]; - if (line[0] == '#' || line[0] == 0) - continue; + linebuf_add_char(buf, child->c); + trie_fnmatch_f(trie, trie_node_from_off(trie, child->child_off), 0, buf, search, cb); + linebuf_rem_char(buf); + } - if (strspn(line, HEXCHARS) == 4) { - unsigned u; + if (node->values_count && fnmatch(linebuf_get(buf), search, 0) == 0) + for (i = 0; i < node->values_count; i++) + cb(trie, trie_string(trie, trie_node_values(trie, node)[i].key_off), + trie_string(trie, trie_node_values(trie, node)[i].value_off)); - if (found_vendor) - break; + linebuf_rem(buf, len); +} - if (sscanf(line, "%04x", &u) == 1 && u == vid) { - char *t; +static void trie_search_f(struct trie_f *trie, const char *search, + void (*cb)(struct trie_f *trie, const char *key, const char *value)) { + struct linebuf buf; + const struct trie_node_f *node; + size_t i = 0; - t = line+4; - t += strspn(t, WHITESPACE); + linebuf_init(&buf); - if (!(*vendor = strdup(t))) { - fprintf(stderr, "Out of memory.\n"); - goto finish; - } + node = trie_node_from_off(trie, trie->head->nodes_root_off); + while (node) { + const struct trie_node_f *child; + size_t p = 0; - found_vendor = 1; - } + if (node->prefix_off) { + uint8_t c; - continue; + for (; (c = trie_string(trie, node->prefix_off)[p]); p++) { + if (c == '*' || c == '?' || c == '[') { + trie_fnmatch_f(trie, node, p, &buf, search + i + p, cb); + return; + } + if (c != search[i + p]) + return; + } + i += p; } - if (found_vendor && line[0] == '\t' && strspn(line+1, HEXCHARS) == 4) { - unsigned u; + child = node_lookup_f(trie, node, '*'); + if (child) { + linebuf_add_char(&buf, '*'); + trie_fnmatch_f(trie, child, 0, &buf, search + i, cb); + linebuf_rem_char(&buf); + } - if (sscanf(line+1, "%04x", &u) == 1 && u == pid) { - char *t; + child = node_lookup_f(trie, node, '?'); + if (child) { + linebuf_add_char(&buf, '?'); + trie_fnmatch_f(trie, child, 0, &buf, search + i, cb); + linebuf_rem_char(&buf); + } - t = line+5; - t += strspn(t, WHITESPACE); + child = node_lookup_f(trie, node, '['); + if (child) { + linebuf_add_char(&buf, '['); + trie_fnmatch_f(trie, child, 0, &buf, search + i, cb); + linebuf_rem_char(&buf); + } - if (!(*product = strdup(t))) { - fprintf(stderr, "Out of memory.\n"); - goto finish; - } + if (search[i] == '\0') { + size_t n; - break; - } + for (n = 0; n < node->values_count; n++) + cb(trie, trie_string(trie, trie_node_values(trie, node)[n].key_off), + trie_string(trie, trie_node_values(trie, node)[n].value_off)); + return; } + + child = node_lookup_f(trie, node, search[i]); + node = child; + i++; } +} - ret = 0; +static void value_cb(struct trie_f *trie, const char *key, const char *value) { + /* TODO: add sub-matches (+) against DMI data */ + if (key[0] == ' ') + udev_builtin_add_property(trie->dev, trie->test, key + 1, value); +} -finish: - free(line); - fclose(f); +static struct trie_f trie; - if (ret < 0) { - free(*product); - free(*vendor); +static int hwdb_lookup(struct udev_device *dev, const char *subsys) { + struct udev_device *d; + const char *modalias; + char str[UTIL_NAME_SIZE]; + int rc = EXIT_SUCCESS; - *product = *vendor = NULL; - } + /* search the first parent device with a modalias */ + for (d = dev; d; d = udev_device_get_parent(d)) { + const char *dsubsys = udev_device_get_subsystem(d); - return ret; -} + /* look only at devices of a specific subsystem */ + if (subsys && dsubsys && !streq(dsubsys, subsys)) + continue; -static struct udev_device *find_device(struct udev_device *dev, const char *subsys, const char *devtype) -{ - const char *str; - - str = udev_device_get_subsystem(dev); - if (str == NULL) - goto try_parent; - if (strcmp(str, subsys) != 0) - goto try_parent; - - if (devtype != NULL) { - str = udev_device_get_devtype(dev); - if (str == NULL) - goto try_parent; - if (strcmp(str, devtype) != 0) - goto try_parent; + modalias = udev_device_get_property_value(d, "MODALIAS"); + if (modalias) + break; + + /* the usb_device does not have modalias, compose one */ + if (dsubsys && streq(dsubsys, "usb")) { + const char *v, *p; + int vn, pn; + + v = udev_device_get_sysattr_value(d, "idVendor"); + if (!v) + continue; + p = udev_device_get_sysattr_value(d, "idProduct"); + if (!p) + continue; + vn = strtol(v, NULL, 16); + if (vn <= 0) + continue; + pn = strtol(p, NULL, 16); + if (pn <= 0) + continue; + snprintf(str, sizeof(str), "usb:v%04Xp%04X*", vn, pn); + modalias = str; + break; + } } - return dev; -try_parent: - return udev_device_get_parent_with_subsystem_devtype(dev, subsys, devtype); + if (!modalias) + return EXIT_FAILURE; + + trie_search_f(&trie, modalias, value_cb); + return rc; } +static int builtin_hwdb(struct udev_device *dev, int argc, char *argv[], bool test) { + static const struct option options[] = { + { "subsystem", required_argument, NULL, 's' }, + {} + }; + const char *subsys = NULL; -static int builtin_db(struct udev_device *dev, bool test, - const char *database, - const char *vendor_attr, const char *product_attr, - const char *subsys, const char *devtype) -{ - struct udev_device *parent; - uint16_t vid = 0, pid = 0; - char *vendor = NULL, *product = NULL; - - parent = find_device(dev, subsys, devtype); - if (!parent) { - fprintf(stderr, "Failed to find device.\n"); - goto finish; + for (;;) { + int option; + + option = getopt_long(argc, argv, "s", options, NULL); + if (option == -1) + break; + + switch (option) { + case 's': + subsys = optarg; + break; + } } - if (get_vid_pid(parent, vendor_attr, product_attr, &vid, &pid) < 0) - goto finish; + trie.dev = dev; + trie.test = test; + if (hwdb_lookup(dev, subsys) < 0) + return EXIT_FAILURE; + return EXIT_SUCCESS; +} - if (lookup_vid_pid(database, vid, pid, &vendor, &product) < 0) - goto finish; +/* called at udev startup and reload */ +static int builtin_hwdb_init(struct udev *udev) +{ + struct stat st; + const char sig[] = HWDB_SIG; + + trie.f = fopen(SYSCONFDIR "/udev/hwdb.bin", "re"); + if (!trie.f) + return -errno; + + if (fstat(fileno(trie.f), &st) < 0 || (size_t)st.st_size < offsetof(struct trie_header_f, strings_len) + 8) { + log_error("Error reading '%s'.", SYSCONFDIR "/udev/hwdb.bin: %m"); + fclose(trie.f); + zero(trie); + return -EINVAL; + } - if (vendor) - udev_builtin_add_property(dev, test, "ID_VENDOR_FROM_DATABASE", vendor); - if (product) - udev_builtin_add_property(dev, test, "ID_MODEL_FROM_DATABASE", product); + trie.map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fileno(trie.f), 0); + if (trie.map == MAP_FAILED) { + log_error("Error mapping '%s'.", SYSCONFDIR "/udev/hwdb.bin: %m"); + fclose(trie.f); + return -EINVAL; + } + trie.file_time_usec = ts_usec(&st.st_mtim); + trie.map_size = st.st_size; + + if (memcmp(trie.map, sig, sizeof(trie.head->signature)) != 0 || (size_t)st.st_size != le64toh(trie.head->file_size)) { + log_error("Unable to recognize the format of '%s'.", SYSCONFDIR "/udev/hwdb.bin"); + log_error("Please try 'udevadm hwdb --update' to re-create it."); + munmap((void *)trie.map, st.st_size); + fclose(trie.f); + zero(trie); + return EINVAL; + } -finish: - free(vendor); - free(product); + log_debug("=== trie on-disk ===\n"); + log_debug("tool version: %llu", (unsigned long long)le64toh(trie.head->tool_version)); + log_debug("file size: %8zi bytes\n", st.st_size); + log_debug("header size %8zu bytes\n", (size_t)le64toh(trie.head->header_size)); + log_debug("strings %8zu bytes\n", (size_t)le64toh(trie.head->strings_len)); + log_debug("nodes %8zu bytes\n", (size_t)le64toh(trie.head->nodes_len)); return 0; } -static int builtin_usb_db(struct udev_device *dev, int argc, char *argv[], bool test) +/* called on udev shutdown and reload request */ +static void builtin_hwdb_exit(struct udev *udev) { - return builtin_db(dev, test, USB_DATABASE, "idVendor", "idProduct", "usb", "usb_device"); + if (!trie.f) + return; + munmap((void *)trie.map, trie.map_size); + fclose(trie.f); + zero(trie); } -static int builtin_pci_db(struct udev_device *dev, int argc, char *argv[], bool test) +/* called every couple of seconds during event activity; 'true' if config has changed */ +static bool builtin_hwdb_validate(struct udev *udev) { - return builtin_db(dev, test, PCI_DATABASE, "vendor", "device", "pci", NULL); -} + struct stat st; -const struct udev_builtin udev_builtin_usb_db = { - .name = "usb-db", - .cmd = builtin_usb_db, - .help = "USB vendor/product database", - .run_once = true, -}; + if (fstat(fileno(trie.f), &st) < 0) + return true; + if (trie.file_time_usec != ts_usec(&st.st_mtim)) + return true; + return false; +} -const struct udev_builtin udev_builtin_pci_db = { - .name = "pci-db", - .cmd = builtin_pci_db, - .help = "PCI vendor/product database", +const struct udev_builtin udev_builtin_hwdb = { + .name = "hwdb", + .cmd = builtin_hwdb, + .init = builtin_hwdb_init, + .exit = builtin_hwdb_exit, + .validate = builtin_hwdb_validate, + .help = "hardware database", .run_once = true, }; |