chiark / gitweb /
udev: hwdb - do not look at "usb_device" parents
[elogind.git] / src / udev / udev-builtin-hwdb.c
index 0db21641b1721693b0734d942ea7166ecda773c4..695a31a12ff31acca95f4b49dc8c22b72b3aa006 100644 (file)
@@ -1,22 +1,21 @@
-/*
- * 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@vrfy.org>
+
+  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>
 #include <inttypes.h>
 #include <ctype.h>
 #include <stdlib.h>
+#include <fnmatch.h>
+#include <getopt.h>
 
 #include "udev.h"
 
-static int get_id_attr(
-        struct udev_device *parent,
-        const char *name,
-        uint16_t *value) {
+static struct udev_hwdb *hwdb;
 
-        const char *t;
-        unsigned u;
+int udev_builtin_hwdb_lookup(struct udev_device *dev,
+                             const char *prefix, const char *modalias,
+                             const char *filter, bool test) {
+        struct udev_list_entry *list;
+        struct udev_list_entry *entry;
+        int n = 0;
 
-        if (!(t = udev_device_get_sysattr_value(parent, name))) {
-                fprintf(stderr, "%s lacks %s.\n", udev_device_get_syspath(parent), name);
-                return -1;
-        }
+        if (!hwdb)
+                return -ENOENT;
 
-        if (startswith(t, "0x"))
-                t += 2;
+        if (prefix) {
+                _cleanup_free_ const char *lookup;
 
-        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;
-        }
+                lookup = strjoin(prefix, modalias, NULL);
+                if (!lookup)
+                        return -ENOMEM;
+                list = udev_hwdb_get_properties_list_entry(hwdb, lookup, 0);
+        } else
+                list = udev_hwdb_get_properties_list_entry(hwdb, modalias, 0);
 
-        *value = (uint16_t) u;
-        return 0;
-}
+        udev_list_entry_foreach(entry, list) {
+                if (filter && fnmatch(filter, udev_list_entry_get_name(entry), FNM_NOESCAPE) != 0)
+                        continue;
 
-static int get_vid_pid(
-        struct udev_device *parent,
-        const char *vendor_attr,
-        const char *product_attr,
-        uint16_t *vid,
-        uint16_t *pid) {
-
-        if (get_id_attr(parent, vendor_attr, vid) < 0)
-                return -1;
-        else if (*vid <= 0) {
-                fprintf(stderr, "Invalid vendor id.\n");
-                return -1;
+                if (udev_builtin_add_property(dev, test,
+                                              udev_list_entry_get_name(entry),
+                                              udev_list_entry_get_value(entry)) < 0)
+                        return -ENOMEM;
+                n++;
         }
-
-        if (get_id_attr(parent, product_attr, pid) < 0)
-                return -1;
-
-        return 0;
+        return n;
 }
 
-static void rstrip(char *n) {
-        size_t i;
-
-        for (i = strlen(n); i > 0 && isspace(n[i-1]); i--)
-                n[i-1] = 0;
+static const char *modalias_usb(struct udev_device *dev, char *s, size_t size) {
+        const char *v, *p;
+        int vn, pn;
+
+        v = udev_device_get_sysattr_value(dev, "idVendor");
+        if (!v)
+                return NULL;
+        p = udev_device_get_sysattr_value(dev, "idProduct");
+        if (!p)
+                return NULL;
+        vn = strtol(v, NULL, 16);
+        if (vn <= 0)
+                return NULL;
+        pn = strtol(p, NULL, 16);
+        if (pn <= 0)
+                return NULL;
+        snprintf(s, size, "usb:v%04Xp%04X*", vn, pn);
+        return s;
 }
 
-#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 udev_builtin_hwdb_search(struct udev_device *dev, struct udev_device *srcdev,
+                                    const char *subsystem, const char *prefix,
+                                    const char *filter, bool test) {
+        struct udev_device *d;
+        char s[16];
+        bool last = false;
+        int r = 0;
 
-        FILE *f;
-        int ret = -1;
-        int found_vendor = 0;
-        char *line = NULL;
+        for (d = srcdev; d && !last; d = udev_device_get_parent(d)) {
+                const char *dsubsys;
+                const char *modalias = NULL;
 
-        *vendor = *product = NULL;
+                dsubsys = udev_device_get_subsystem(d);
+                if (!dsubsys)
+                        continue;
 
-        if (!(f = fopen(database, "rme"))) {
-                fprintf(stderr, "Failed to open database file '%s': %s\n", database, strerror(errno));
-                return -1;
-        }
+                /* look only at devices of a specific subsystem */
+                if (subsystem && !streq(dsubsys, subsystem))
+                        continue;
 
-        for (;;) {
-                size_t n;
+                modalias = udev_device_get_property_value(d, "MODALIAS");
 
-                if (getline(&line, &n, f) < 0)
-                        break;
+                if (streq(dsubsys, "usb") && streq_ptr(udev_device_get_devtype(d), "usb_device")) {
+                        /* if the usb_device does not have a modalias, compose one */
+                        if (!modalias)
+                                modalias = modalias_usb(d, s, sizeof(s));
 
-                rstrip(line);
+                        /* avoid looking at any parent device, they are usually just a USB hub */
+                        last = true;
+                }
 
-                if (line[0] == '#' || line[0] == 0)
+                if (!modalias)
                         continue;
 
-                if (strspn(line, HEXCHARS) == 4) {
-                        unsigned u;
-
-                        if (found_vendor)
-                                break;
-
-                        if (sscanf(line, "%04x", &u) == 1 && u == vid) {
-                                char *t;
-
-                                t = line+4;
-                                t += strspn(t, WHITESPACE);
+                r = udev_builtin_hwdb_lookup(dev, prefix, modalias, filter, test);
+                if (r > 0)
+                        break;
+        }
 
-                                if (!(*vendor = strdup(t))) {
-                                        fprintf(stderr, "Out of memory.\n");
-                                        goto finish;
-                                }
+        return r;
+}
 
-                                found_vendor = 1;
-                        }
+static int builtin_hwdb(struct udev_device *dev, int argc, char *argv[], bool test) {
+        static const struct option options[] = {
+                { "filter", required_argument, NULL, 'f' },
+                { "device", required_argument, NULL, 'd' },
+                { "subsystem", required_argument, NULL, 's' },
+                { "lookup-prefix", required_argument, NULL, 'p' },
+                {}
+        };
+        const char *filter = NULL;
+        const char *device = NULL;
+        const char *subsystem = NULL;
+        const char *prefix = NULL;
+        struct udev_device *srcdev;
+
+        if (!hwdb)
+                return EXIT_FAILURE;
 
-                        continue;
-                }
+        for (;;) {
+                int option;
 
-                if (found_vendor && line[0] == '\t' && strspn(line+1, HEXCHARS) == 4) {
-                        unsigned u;
+                option = getopt_long(argc, argv, "f:d:s:p:", options, NULL);
+                if (option == -1)
+                        break;
 
-                        if (sscanf(line+1, "%04x", &u) == 1 && u == pid) {
-                                char *t;
+                switch (option) {
+                case 'f':
+                        filter = optarg;
+                        break;
 
-                                t = line+5;
-                                t += strspn(t, WHITESPACE);
+                case 'd':
+                        device = optarg;
+                        break;
 
-                                if (!(*product = strdup(t))) {
-                                        fprintf(stderr, "Out of memory.\n");
-                                        goto finish;
-                                }
+                case 's':
+                        subsystem = optarg;
+                        break;
 
-                                break;
-                        }
+                case 'p':
+                        prefix = optarg;
+                        break;
                 }
         }
 
-        ret = 0;
-
-finish:
-        free(line);
-        fclose(f);
-
-        if (ret < 0) {
-                free(*product);
-                free(*vendor);
-
-                *product = *vendor = NULL;
+        /* query a specific key given as argument */
+        if (argv[optind]) {
+                if (udev_builtin_hwdb_lookup(dev, prefix, argv[optind], filter, test) > 0)
+                        return EXIT_SUCCESS;
+                return EXIT_FAILURE;
         }
 
-        return ret;
+        /* read data from another device than the device we will store the data */
+        if (device) {
+                srcdev = udev_device_new_from_device_id(udev_device_get_udev(dev), device);
+                if (!srcdev)
+                        return EXIT_FAILURE;
+        } else
+                srcdev = dev;
+
+        if (udev_builtin_hwdb_search(dev, srcdev, subsystem, prefix, filter, test) > 0)
+                return EXIT_SUCCESS;
+        return EXIT_FAILURE;
 }
 
-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;
-        }
-        return dev;
-try_parent:
-        return udev_device_get_parent_with_subsystem_devtype(dev, subsys, devtype);
-}
-
-
-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;
-        }
-
-        if (get_vid_pid(parent, vendor_attr, product_attr, &vid, &pid) < 0)
-                goto finish;
-
-        if (lookup_vid_pid(database, vid, pid, &vendor, &product) < 0)
-                goto finish;
-
-        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);
-
-finish:
-        free(vendor);
-        free(product);
+/* called at udev startup and reload */
+static int builtin_hwdb_init(struct udev *udev) {
+        if (hwdb)
+                return 0;
+        hwdb = udev_hwdb_new(udev);
+        if (!hwdb)
+                return -ENOMEM;
         return 0;
 }
 
-static int builtin_usb_db(struct udev_device *dev, int argc, char *argv[], bool test)
-{
-        return builtin_db(dev, test, USB_DATABASE, "idVendor", "idProduct", "usb", "usb_device");
+/* called on udev shutdown and reload request */
+static void builtin_hwdb_exit(struct udev *udev) {
+        hwdb = udev_hwdb_unref(hwdb);
 }
 
-static int builtin_pci_db(struct udev_device *dev, int argc, char *argv[], bool test)
-{
-        return builtin_db(dev, test, PCI_DATABASE, "vendor", "device", "pci", NULL);
+/* called every couple of seconds during event activity; 'true' if config has changed */
+static bool builtin_hwdb_validate(struct udev *udev) {
+        return udev_hwdb_validate(hwdb);
 }
 
-const struct udev_builtin udev_builtin_usb_db = {
-        .name = "usb-db",
-        .cmd = builtin_usb_db,
-        .help = "USB vendor/product database",
-        .run_once = true,
-};
-
-const struct udev_builtin udev_builtin_pci_db = {
-        .name = "pci-db",
-        .cmd = builtin_pci_db,
-        .help = "PCI vendor/product database",
-        .run_once = true,
+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",
 };