udev: add hardware database support

This commit is contained in:
Kay Sievers 2012-10-22 18:23:08 +02:00
parent 59bb9d9a14
commit 796b06c21b
18 changed files with 111773 additions and 241 deletions

View File

@ -80,6 +80,7 @@ systempresetdir=$(rootprefix)/lib/systemd/system-preset
udevlibexecdir=$(rootprefix)/lib/udev
udevhomedir = $(udevlibexecdir)
udevrulesdir = $(udevlibexecdir)/rules.d
udevhwdbdir = $(udevlibexecdir)/hwdb.d
# And these are the special ones for /
rootprefix=@rootprefix@
@ -1758,6 +1759,7 @@ man/systemd-udevd-kernel.socket.8: man/systemd-udevd.service.8
udev-confdirs:
-$(MKDIR_P) $(DESTDIR)$(sysconfdir)/udev/rules.d
-$(MKDIR_P) $(DESTDIR)$(sysconfdir)/udev/hwdb.d
INSTALL_DATA_HOOKS += udev-confdirs
@ -1777,6 +1779,10 @@ dist_udevrules_DATA += \
rules/80-drivers.rules \
rules/95-udev-late.rules
dist_udevhwdb_DATA = \
hwdb/20-pci-vendor-product.hwdb \
hwdb/20-usb-vendor-product.hwdb
udevconfdir = $(sysconfdir)/udev
dist_udevconf_DATA = \
src/udev/udev.conf
@ -1824,6 +1830,7 @@ noinst_LTLIBRARIES += \
libudev_core_la_SOURCES = \
src/udev/udev.h \
src/udev/udev-hwdb.h \
src/udev/udev-event.c \
src/udev/udev-watch.c \
src/udev/udev-node.c \
@ -1854,8 +1861,7 @@ libudev_core_la_LIBADD = \
libudev_core_la_CPPFLAGS = \
$(AM_CPPFLAGS) \
-DFIRMWARE_PATH="$(FIRMWARE_PATH)" \
-DUSB_DATABASE=\"$(USB_DATABASE)\" -DPCI_DATABASE=\"$(PCI_DATABASE)\"
-DFIRMWARE_PATH="$(FIRMWARE_PATH)"
if HAVE_ACL
libudev_core_la_SOURCES += \
@ -1878,6 +1884,7 @@ udevadm_SOURCES = \
src/udev/udevadm-info.c \
src/udev/udevadm-control.c \
src/udev/udevadm-monitor.c \
src/udev/udevadm-hwdb.c \
src/udev/udevadm-settle.c \
src/udev/udevadm-trigger.c \
src/udev/udevadm-test.c \
@ -3951,6 +3958,7 @@ distclean-local: $(DISTCLEAN_LOCAL_HOOKS)
clean-local:
rm -rf $(abs_srcdir)/install-tree
rm -f $(abs_srcdir)/hwdb/usb.ids $(abs_srcdir)/hwdb/pci.ids
DISTCHECK_CONFIGURE_FLAGS = \
--with-sysvinit-path=$$dc_install_base/$(sysvinitdir) \
@ -3967,6 +3975,12 @@ DISTCHECK_CONFIGURE_FLAGS += \
--enable-gtk-doc
endif
hwdb-update:
( cd hwdb && \
wget -N http://www.linux-usb.org/usb.ids && \
wget -N http://pciids.sourceforge.net/v2.2/pci.ids && \
./ids-update.pl )
upload: all distcheck
cp -v systemd-$(VERSION).tar.xz /home/lennart/git.fedora/systemd/
scp systemd-$(VERSION).tar.xz fdo:/srv/www.freedesktop.org/www/software/systemd/

1
TODO
View File

@ -448,6 +448,7 @@ Features:
* GC unreferenced jobs (such as .device jobs)
* write blog stories about:
- hwdb: what belongs into it, lsusb
- enabling dbus services
- status update
- how to make changes to sysctl and sysfs attributes

View File

@ -567,33 +567,6 @@ if test "x$enable_coredump" != "xno"; then
fi
AM_CONDITIONAL(ENABLE_COREDUMP, [test "$have_coredump" = "yes"])
# ------------------------------------------------------------------------------
AC_ARG_WITH(usb-ids-path,
[AS_HELP_STRING([--with-usb-ids-path=DIR], [Path to usb.ids file])],
[USB_DATABASE=${withval}],
[if test -n "$usbids" ; then
USB_DATABASE="$usbids"
else
PKG_CHECK_MODULES(USBUTILS, usbutils >= 0.82)
AC_SUBST([USB_DATABASE], [$($PKG_CONFIG --variable=usbids usbutils)])
fi])
AC_MSG_CHECKING([for USB database location])
AC_MSG_RESULT([$USB_DATABASE])
AC_SUBST(USB_DATABASE)
AC_ARG_WITH(pci-ids-path,
[AS_HELP_STRING([--with-pci-ids-path=DIR], [Path to pci.ids file])],
[PCI_DATABASE=${withval}],
[if test -n "$pciids" ; then
PCI_DATABASE="$pciids"
else
PKG_CHECK_MODULES(LIBPCI, libpci >= 3)
AC_SUBST([PCI_DATABASE], [$($PKG_CONFIG --variable=idsdir libpci)/pci.ids])
fi])
AC_MSG_CHECKING([for PCI database location])
AC_MSG_RESULT([$PCI_DATABASE])
AC_SUBST(PCI_DATABASE)
# ------------------------------------------------------------------------------
AC_ARG_WITH(firmware-path,
AS_HELP_STRING([--with-firmware-path=DIR[[[:DIR[...]]]]],
@ -878,8 +851,6 @@ AC_MSG_RESULT([
localed: ${have_localed}
coredump: ${have_coredump}
firmware path: ${FIRMWARE_PATH}
usb.ids: ${USB_DATABASE}
pci.ids: ${PCI_DATABASE}
gudev: ${enable_gudev}
gintrospection: ${enable_introspection}
keymap: ${enable_keymap}

2
hwdb/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/pci.ids
/usb.ids

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

80
hwdb/ids-update.pl Executable file
View File

@ -0,0 +1,80 @@
#!/usr/bin/perl
use strict;
use warnings;
my $vendor;
open(IN, "<", "usb.ids");
open(OUT, ">", "20-usb-vendor-product.hwdb");
print(OUT "# This file is part of systemd.\n" .
"#\n" .
"# Data imported and updated from: http://www.linux-usb.org/usb.ids\n");
while (my $line = <IN>) {
$line =~ s/\s+$//;
$line =~ m/^([0-9a-f]{4})\s*(.*)$/;
if (defined $1) {
$vendor = uc $1;
my $text = $2;
print(OUT "\n");
print(OUT "usb:v" . $vendor . "*\n");
print(OUT " ID_VENDOR_FROM_DATABASE=" . $text . "\n");
next;
}
$line =~ m/^\t([0-9a-f]{4})\s*(.*)$/;
if (defined $1) {
my $product = uc $1;
my $text = $2;
print(OUT "\n");
print(OUT "usb:v" . $vendor . "p" . $product . "*\n");
print(OUT " ID_PRODUCT_FROM_DATABASE=" . $text . "\n");
}
}
close(INP);
close(OUTP);
my $device;
open(IN, "<", "pci.ids");
open(OUT, ">", "20-pci-vendor-product.hwdb");
print(OUT "# This file is part of systemd.\n" .
"#\n" .
"# Data imported and updated from: http://pciids.sourceforge.net/v2.2/pci.ids\n");
while (my $line = <IN>) {
$line =~ s/\s+$//;
$line =~ m/^([0-9a-f]{4})\s*(.*)$/;
if (defined $1) {
$vendor = uc $1;
my $text = $2;
print(OUT "\n");
print(OUT "pci:v0000" . $vendor . "*\n");
print(OUT " ID_VENDOR_FROM_DATABASE=" . $text . "\n");
next;
}
$line =~ m/^\t([0-9a-f]{4})\s*(.*)$/;
if (defined $1) {
$device = uc $1;
my $text = $2;
print(OUT "\n");
print(OUT "pci:v0000" . $vendor . "d0000" . $device . "*\n");
print(OUT " ID_PRODUCT_FROM_DATABASE=" . $text . "\n");
next;
}
$line =~ m/^\t\t([0-9a-f]{4})\s*([0-9a-f]{4})\s*(.*)$/;
if (defined $1) {
my $sub_vendor = uc $1;
my $sub_device = uc $2;
my $text = $3;
print(OUT "\n");
print(OUT "pci:v0000" . $vendor . "d0000" . $device . "sv0000" . $sub_vendor . "sd0000" . $sub_device . "*\n");
print(OUT " ID_PRODUCT_FROM_DATABASE=" . $text . "\n");
}
}
close(INP);
close(OUTP);

View File

@ -43,7 +43,7 @@ SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x014001*", GROUP="video"
# 'libusb' device nodes
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", MODE="0664"
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", IMPORT{builtin}="usb_id"
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb"
# printer
KERNEL=="parport[0-9]*", GROUP="lp"

View File

@ -4,11 +4,11 @@ ACTION=="remove", GOTO="net_end"
SUBSYSTEM!="net", GOTO="net_end"
SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{builtin}="usb_id"
SUBSYSTEMS=="usb", IMPORT{builtin}="usb-db"
SUBSYSTEMS=="usb", IMPORT{builtin}="hwdb"
SUBSYSTEMS=="usb", ATTRS{idVendor}!="", ATTRS{idProduct}!="", ENV{ID_VENDOR_ID}="$attr{idVendor}", ENV{ID_MODEL_ID}="$attr{idProduct}"
SUBSYSTEMS=="usb", GOTO="net_end"
SUBSYSTEMS=="pci", IMPORT{builtin}="pci-db"
IMPORT{builtin}="hwdb"
SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
LABEL="net_end"

View File

@ -3,12 +3,12 @@
ACTION=="remove", GOTO="tty_end"
SUBSYSTEM!="tty", GOTO="tty_end"
IMPORT{builtin}="hwdb"
SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{builtin}="usb_id"
SUBSYSTEMS=="usb", IMPORT{builtin}="usb-db"
SUBSYSTEMS=="usb", ATTRS{idVendor}!="", ATTRS{idProduct}!="", ENV{ID_VENDOR_ID}="$attr{idVendor}", ENV{ID_MODEL_ID}="$attr{idProduct}"
SUBSYSTEMS=="usb", GOTO="tty_end"
SUBSYSTEMS=="pci", IMPORT{builtin}="pci-db"
SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
LABEL="tty_end"

View File

@ -37,8 +37,8 @@ KERNEL!="card*", GOTO="sound_end"
ENV{SOUND_INITIALIZED}="1"
IMPORT{builtin}="hwdb"
SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
SUBSYSTEMS=="usb", IMPORT{builtin}="usb-db"
SUBSYSTEMS=="usb", GOTO="skip_pci"
SUBSYSTEMS=="firewire", ATTRS{vendor_name}=="?*", ATTRS{model_name}=="?*", \
@ -46,10 +46,7 @@ SUBSYSTEMS=="firewire", ATTRS{vendor_name}=="?*", ATTRS{model_name}=="?*", \
SUBSYSTEMS=="firewire", ATTRS{guid}=="?*", ENV{ID_ID}="firewire-$attr{guid}"
SUBSYSTEMS=="firewire", GOTO="skip_pci"
SUBSYSTEMS=="pci", IMPORT{builtin}="pci-db"
SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
LABEL="skip_pci"
ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="?*", ENV{ID_ID}="$env{ID_BUS}-$env{ID_SERIAL}-$env{ID_USB_INTERFACE_NUM}-$attr{id}"

View File

@ -1379,8 +1379,7 @@ int main(int argc, char *argv[]) {
} else {
char **files, **f;
r = conf_files_list_strv(&files, ".conf",
(const char **) conf_file_dirs);
r = conf_files_list_strv(&files, ".conf", (const char **)conf_file_dirs);
if (r < 0) {
log_error("Failed to enumerate tmpfiles.d files: %s", strerror(-r));
r = EXIT_FAILURE;

View File

@ -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;
if (!(t = udev_device_get_sysattr_value(parent, name))) {
fprintf(stderr, "%s lacks %s.\n", udev_device_get_syspath(parent), name);
return -1;
static void linebuf_init(struct linebuf *buf) {
buf->size = 0;
buf->len = 0;
}
if (startswith(t, "0x"))
t += 2;
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 const char *linebuf_get(struct linebuf *buf) {
if (buf->len + 1 >= sizeof(buf->bytes))
return NULL;
buf->bytes[buf->len] = '\0';
return buf->bytes;
}
*value = (uint16_t) u;
return 0;
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;
}
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 (get_id_attr(parent, product_attr, pid) < 0)
return -1;
return 0;
}
static void rstrip(char *n) {
size_t i;
for (i = strlen(n); i > 0 && isspace(n[i-1]); i--)
n[i-1] = 0;
}
#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 bool linebuf_add_char(struct linebuf *buf, char c)
{
FILE *f;
int ret = -1;
int found_vendor = 0;
char *line = NULL;
*vendor = *product = NULL;
if (!(f = fopen(database, "rme"))) {
fprintf(stderr, "Failed to open database file '%s': %s\n", database, strerror(errno));
return -1;
if (buf->len + 1 >= sizeof(buf->bytes))
return false;
buf->bytes[buf->len++] = c;
return true;
}
for (;;) {
static void linebuf_rem(struct linebuf *buf, size_t count) {
assert(buf->len >= count);
buf->len -= count;
}
static void linebuf_rem_char(struct linebuf *buf) {
linebuf_rem(buf, 1);
}
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;
};
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));
}
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;
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 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));
}
static const char *trie_string(struct trie_f *trie, le64_t off) {
return trie->map + le64toh(off);
}
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;
return n1->c - n2->c;
}
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;
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;
}
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;
prefix = trie_string(trie, node->prefix_off);
len = strlen(prefix + p);
linebuf_add(buf, prefix + p, len);
for (i = 0; i < node->children_count; i++) {
const struct trie_child_entry_f *child = &trie_node_children(trie, node)[i];
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 (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));
linebuf_rem(buf, len);
}
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;
linebuf_init(&buf);
node = trie_node_from_off(trie, trie->head->nodes_root_off);
while (node) {
const struct trie_node_f *child;
size_t p = 0;
if (node->prefix_off) {
uint8_t c;
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;
}
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);
}
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);
}
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 (search[i] == '\0') {
size_t n;
if (getline(&line, &n, f) < 0)
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;
}
rstrip(line);
child = node_lookup_f(trie, node, search[i]);
node = child;
i++;
}
}
if (line[0] == '#' || line[0] == 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);
}
static struct trie_f trie;
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;
/* 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);
/* look only at devices of a specific subsystem */
if (subsys && dsubsys && !streq(dsubsys, subsys))
continue;
if (strspn(line, HEXCHARS) == 4) {
unsigned u;
if (found_vendor)
modalias = udev_device_get_property_value(d, "MODALIAS");
if (modalias)
break;
if (sscanf(line, "%04x", &u) == 1 && u == vid) {
char *t;
t = line+4;
t += strspn(t, WHITESPACE);
if (!(*vendor = strdup(t))) {
fprintf(stderr, "Out of memory.\n");
goto finish;
}
found_vendor = 1;
}
/* 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;
}
if (found_vendor && line[0] == '\t' && strspn(line+1, HEXCHARS) == 4) {
unsigned u;
if (sscanf(line+1, "%04x", &u) == 1 && u == pid) {
char *t;
t = line+5;
t += strspn(t, WHITESPACE);
if (!(*product = strdup(t))) {
fprintf(stderr, "Out of memory.\n");
goto finish;
}
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;
}
}
if (!modalias)
return EXIT_FAILURE;
trie_search_f(&trie, modalias, value_cb);
return rc;
}
ret = 0;
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;
finish:
free(line);
fclose(f);
for (;;) {
int option;
if (ret < 0) {
free(*product);
free(*vendor);
option = getopt_long(argc, argv, "s", options, NULL);
if (option == -1)
break;
*product = *vendor = NULL;
switch (option) {
case 's':
subsys = optarg;
break;
}
}
return ret;
trie.dev = dev;
trie.test = test;
if (hwdb_lookup(dev, subsys) < 0)
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
static struct udev_device *find_device(struct udev_device *dev, const char *subsys, const char *devtype)
/* called at udev startup and reload */
static int builtin_hwdb_init(struct udev *udev)
{
const char *str;
struct stat st;
const char sig[] = HWDB_SIG;
str = udev_device_get_subsystem(dev);
if (str == NULL)
goto try_parent;
if (strcmp(str, subsys) != 0)
goto try_parent;
trie.f = fopen(SYSCONFDIR "/udev/hwdb.bin", "re");
if (!trie.f)
return -errno;
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);
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;
}
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;
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 (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;
}
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);
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;
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_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",
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,
};

View File

@ -31,11 +31,10 @@ static const struct udev_builtin *builtins[] = {
[UDEV_BUILTIN_BLKID] = &udev_builtin_blkid,
[UDEV_BUILTIN_BTRFS] = &udev_builtin_btrfs,
[UDEV_BUILTIN_FIRMWARE] = &udev_builtin_firmware,
[UDEV_BUILTIN_HWDB] = &udev_builtin_hwdb,
[UDEV_BUILTIN_INPUT_ID] = &udev_builtin_input_id,
[UDEV_BUILTIN_KMOD] = &udev_builtin_kmod,
[UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id,
[UDEV_BUILTIN_PCI_DB] = &udev_builtin_pci_db,
[UDEV_BUILTIN_USB_DB] = &udev_builtin_usb_db,
[UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id,
#ifdef HAVE_ACL
[UDEV_BUILTIN_UACCESS] = &udev_builtin_uaccess,
@ -128,7 +127,7 @@ int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const c
int argc;
char *argv[128];
optind = 0;
optind = 1;
util_strscpy(arg, sizeof(arg), command);
udev_build_argv(udev_device_get_udev(dev), arg, &argc, argv);
return builtins[cmd]->cmd(dev, argc, argv, test);

69
src/udev/udev-hwdb.h Normal file
View File

@ -0,0 +1,69 @@
/***
This file is part of systemd.
Copyright 2012 Kay Sievers <kay.sievers@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 "sparse-endian.h"
#define HWDB_SIG { 'K', 'S', 'L', 'P', 'H', 'H', 'R', 'H' }
/* on-disk trie objects */
_packed_ struct trie_header_f {
uint8_t signature[8];
/* version of tool which created the file */
le64_t tool_version;
le64_t file_size;
/* size of structures to allow them to grow */
le64_t header_size;
le64_t node_size;
le64_t child_entry_size;
le64_t value_entry_size;
/* offset of the root trie node */
le64_t nodes_root_off;
/* size of the nodes and string section */
le64_t nodes_len;
le64_t strings_len;
};
_packed_ struct trie_node_f {
/* prefix of lookup string, shared by all children */
le64_t prefix_off;
/* size of children entry array appended to the node */
uint8_t children_count;
uint8_t padding[7];
/* size of value entry array appended to the node */
le64_t values_count;
};
/* array of child entries, follows directly the node record */
_packed_ struct trie_child_entry_f {
/* index of the child node */
uint8_t c;
uint8_t padding[7];
/* offset of the child node */
le64_t child_off;
};
/* array of value entries, follows directly the node record/child array */
_packed_ struct trie_value_entry_f {
le64_t key_off;
le64_t value_off;
};

View File

@ -137,11 +137,10 @@ enum udev_builtin_cmd {
UDEV_BUILTIN_BLKID,
UDEV_BUILTIN_BTRFS,
UDEV_BUILTIN_FIRMWARE,
UDEV_BUILTIN_HWDB,
UDEV_BUILTIN_INPUT_ID,
UDEV_BUILTIN_KMOD,
UDEV_BUILTIN_PATH_ID,
UDEV_BUILTIN_PCI_DB,
UDEV_BUILTIN_USB_DB,
UDEV_BUILTIN_USB_ID,
#ifdef HAVE_ACL
UDEV_BUILTIN_UACCESS,
@ -160,11 +159,10 @@ struct udev_builtin {
extern const struct udev_builtin udev_builtin_blkid;
extern const struct udev_builtin udev_builtin_btrfs;
extern const struct udev_builtin udev_builtin_firmware;
extern const struct udev_builtin udev_builtin_hwdb;
extern const struct udev_builtin udev_builtin_input_id;
extern const struct udev_builtin udev_builtin_kmod;
extern const struct udev_builtin udev_builtin_path_id;
extern const struct udev_builtin udev_builtin_pci_db;
extern const struct udev_builtin udev_builtin_usb_db;
extern const struct udev_builtin udev_builtin_usb_id;
extern const struct udev_builtin udev_builtin_uaccess;
int udev_builtin_init(struct udev *udev);
@ -194,6 +192,7 @@ extern const struct udevadm_cmd udevadm_trigger;
extern const struct udevadm_cmd udevadm_settle;
extern const struct udevadm_cmd udevadm_control;
extern const struct udevadm_cmd udevadm_monitor;
extern const struct udevadm_cmd udevadm_hwdb;
extern const struct udevadm_cmd udevadm_test;
extern const struct udevadm_cmd udevadm_test_builtin;
#endif

548
src/udev/udevadm-hwdb.c Normal file
View File

@ -0,0 +1,548 @@
/***
This file is part of systemd.
Copyright 2012 Kay Sievers <kay.sievers@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 <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include "util.h"
#include "strbuf.h"
#include "conf-files.h"
#include "udev.h"
#include "udev-hwdb.h"
/*
* Generic udev properties, key/value database based on modalias strings.
* Uses a Patricia/radix trie to index all matches for efficient lookup.
*/
static const char * const conf_file_dirs[] = {
SYSCONFDIR "/udev/hwdb.d",
UDEVLIBEXECDIR "/hwdb.d",
NULL
};
/* in-memory trie objects */
struct trie {
struct trie_node *root;
struct strbuf *strings;
size_t nodes_count;
size_t children_count;
size_t values_count;
};
struct trie_node {
/* prefix, common part for all children of this node */
size_t prefix_off;
/* sorted array of pointers to children nodes */
struct trie_child_entry *children;
uint8_t children_count;
/* sorted array of key/value pairs */
struct trie_value_entry *values;
size_t values_count;
};
/* children array item with char (0-255) index */
struct trie_child_entry {
uint8_t c;
struct trie_node *child;
};
/* value array item with key/value pairs */
struct trie_value_entry {
size_t key_off;
size_t value_off;
};
static int trie_children_cmp(const void *v1, const void *v2) {
const struct trie_child_entry *n1 = v1;
const struct trie_child_entry *n2 = v2;
return n1->c - n2->c;
}
static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) {
struct trie_child_entry *child;
int err = 0;
/* extend array, add new entry, sort for bisection */
child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry));
if (!child) {
err = -ENOMEM;
goto out;
}
node->children = child;
trie->children_count++;
node->children[node->children_count].c = c;
node->children[node->children_count].child = node_child;
node->children_count++;
qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
trie->nodes_count++;
out:
return err;
}
static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) {
struct trie_child_entry *child;
struct trie_child_entry search;
search.c = c;
child = bsearch(&search, node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
if (child)
return child->child;
return NULL;
}
static void trie_node_cleanup(struct trie_node *node) {
size_t i;
for (i = 0; i < node->children_count; i++)
trie_node_cleanup(node->children[i].child);
free(node->children);
free(node->values);
free(node);
}
static int trie_values_cmp(const void *v1, const void *v2, void *arg) {
const struct trie_value_entry *val1 = v1;
const struct trie_value_entry *val2 = v2;
struct trie *trie = arg;
return strcmp(trie->strings->buf + val1->key_off,
trie->strings->buf + val2->key_off);
}
static int trie_node_add_value(struct trie *trie, struct trie_node *node,
const char *key, const char *value) {
size_t k, v;
struct trie_value_entry *val;
struct trie_value_entry search;
k = strbuf_add_string(trie->strings, key, strlen(key));
v = strbuf_add_string(trie->strings, value, strlen(value));
/* replace existing earlier key with new value */
search.value_off = k;
val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
if (val) {
val->value_off = v;
return 0;
}
/* extend array, add new entry, sort for bisection */
val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry));
if (!val)
return -ENOMEM;
trie->values_count++;
node->values = val;
node->values[node->values_count].key_off = k;
node->values[node->values_count].value_off = v;
node->values_count++;
qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
return 0;
}
static int trie_insert(struct trie *trie, struct trie_node *node, const char *search,
const char *key, const char *value) {
size_t i = 0;
int err = 0;
for (;;) {
size_t p;
uint8_t c;
struct trie_node *child;
for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) {
char *s;
ssize_t off;
if (c == search[i + p])
continue;
/* split node */
child = calloc(sizeof(struct trie_node), 1);
if (!child) {
err = -ENOMEM;
goto out;
}
/* move values from parent to child */
child->prefix_off = node->prefix_off + p+1;
child->children = node->children;
child->children_count = node->children_count;
child->values = node->values;
child->values_count = node->values_count;
/* update parent; use strdup() because the source gets realloc()d */
s = strndup(trie->strings->buf + node->prefix_off, p);
if (!s) {
err = -ENOMEM;
goto out;
}
off = strbuf_add_string(trie->strings, s, p);
free(s);
if (off < 0) {
err = off;
goto out;
}
node->prefix_off = off;
node->children = NULL;
node->children_count = 0;
node->values = NULL;
node->values_count = 0;
err = node_add_child(trie, node, child, c);
if (err)
goto out;
break;
}
i += p;
c = search[i];
if (c == '\0')
return trie_node_add_value(trie, node, key, value);
child = node_lookup(node, c);
if (!child) {
ssize_t off;
/* new child */
child = calloc(sizeof(struct trie_node), 1);
if (!child) {
err = -ENOMEM;
goto out;
}
off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1));
if (off < 0) {
err = off;
goto out;
}
child->prefix_off = off;
err = node_add_child(trie, node, child, c);
if (err)
goto out;
return trie_node_add_value(trie, child, key, value);
}
node = child;
i++;
}
out:
return err;
}
struct trie_f {
FILE *f;
struct trie *trie;
uint64_t strings_off;
uint64_t nodes_count;
uint64_t children_count;
uint64_t values_count;
};
/* calculate the storage space for the nodes, children arrays, value arrays */
static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) {
uint64_t i;
for (i = 0; i < node->children_count; i++)
trie_store_nodes_size(trie, node->children[i].child);
trie->strings_off += sizeof(struct trie_node_f);
for (i = 0; i < node->children_count; i++)
trie->strings_off += sizeof(struct trie_child_entry_f);
for (i = 0; i < node->values_count; i++)
trie->strings_off += sizeof(struct trie_value_entry_f);
}
static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) {
uint64_t i;
struct trie_node_f n = {
.prefix_off = htole64(trie->strings_off + node->prefix_off),
.children_count = node->children_count,
.values_count = htole64(node->values_count),
};
struct trie_child_entry_f *children;
int64_t node_off;
if (node->children_count) {
children = new0(struct trie_child_entry_f, node->children_count);
if (!children)
return -ENOMEM;
}
/* post-order recursion */
for (i = 0; i < node->children_count; i++) {
int64_t child_off;
child_off = trie_store_nodes(trie, node->children[i].child);
if (child_off < 0)
return child_off;
children[i].c = node->children[i].c;
children[i].child_off = htole64(child_off);
}
/* write node */
node_off = ftello(trie->f);
fwrite(&n, sizeof(struct trie_node_f), 1, trie->f);
trie->nodes_count++;
/* append children array */
if (node->children_count) {
fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f);
trie->children_count += node->children_count;
free(children);
}
/* append values array */
for (i = 0; i < node->values_count; i++) {
struct trie_value_entry_f v = {
.key_off = htole64(trie->strings_off + node->values[i].key_off),
.value_off = htole64(trie->strings_off + node->values[i].value_off),
};
fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f);
trie->values_count++;
}
return node_off;
}
static int trie_store(struct trie *trie, const char *filename) {
struct trie_f t = {
.trie = trie,
};
char *filename_tmp;
int64_t pos;
int64_t root_off;
int64_t size;
struct trie_header_f h = {
.signature = HWDB_SIG,
.tool_version = htole64(atoi(VERSION)),
.header_size = htole64(sizeof(struct trie_header_f)),
.node_size = htole64(sizeof(struct trie_node_f)),
.child_entry_size = htole64(sizeof(struct trie_child_entry_f)),
.value_entry_size = htole64(sizeof(struct trie_value_entry_f)),
};
int err;
/* calculate size of header, nodes, children entries, value entries */
t.strings_off = sizeof(struct trie_header_f);
trie_store_nodes_size(&t, trie->root);
err = fopen_temporary(filename , &t.f, &filename_tmp);
if (err < 0)
return err;
fchmod(fileno(t.f), 0444);
/* write nodes */
fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET);
root_off = trie_store_nodes(&t, trie->root);
h.nodes_root_off = htole64(root_off);
pos = ftello(t.f);
h.nodes_len = htole64(pos - sizeof(struct trie_header_f));
/* write string buffer */
fwrite(trie->strings->buf, trie->strings->len, 1, t.f);
h.strings_len = htole64(trie->strings->len);
/* write header */
size = ftello(t.f);
h.file_size = htole64(size);
fseeko(t.f, 0, SEEK_SET);
fwrite(&h, sizeof(struct trie_header_f), 1, t.f);
err = ferror(t.f);
if (err)
err = -errno;
fclose(t.f);
if (err < 0 || rename(filename_tmp, filename) < 0) {
unlink(filename_tmp);
goto out;
}
log_debug("=== trie on-disk ===\n");
log_debug("size: %8zi bytes\n", size);
log_debug("header: %8zu bytes\n", sizeof(struct trie_header_f));
log_debug("nodes: %8zu bytes (%8zi)\n", t.nodes_count * sizeof(struct trie_node_f), t.nodes_count);
log_debug("child pointers: %8zu bytes (%8zi)\n", t.children_count * sizeof(struct trie_child_entry_f), t.children_count);
log_debug("value pointers: %8zu bytes (%8zi)\n", t.values_count * sizeof(struct trie_value_entry_f), t.values_count);
log_debug("string store: %8zu bytes\n", trie->strings->len);
log_debug("strings start: %8llu\n", (unsigned long long) t.strings_off);
out:
free(filename_tmp);
return err;
}
static int import_file(struct trie *trie, const char *filename) {
FILE *f;
char line[LINE_MAX];
char match[LINE_MAX];
f = fopen(filename, "re");
if (f == NULL)
return -errno;
match[0] = '\0';
while (fgets(line, sizeof(line), f)) {
size_t len;
if (line[0] == '#')
continue;
/* new line, new record */
if (line[0] == '\n') {
match[0] = '\0';
continue;
}
/* remove newline */
len = strlen(line);
if (len < 2)
continue;
line[len-1] = '\0';
/* start of new record */
if (match[0] == '\0') {
strcpy(match, line);
continue;
}
/* value lines */
if (line[0] == ' ') {
char *value;
value = strchr(line, '=');
if (!value)
continue;
value[0] = '\0';
value++;
trie_insert(trie, trie->root, match, line, value);
}
}
fclose(f);
return 0;
}
static void help(void) {
printf("Usage: udevadm hwdb [--create] [--help]\n"
" --update update the hardware database\n"
" --help\n\n");
}
static int adm_hwdb(struct udev *udev, int argc, char *argv[]) {
static const struct option options[] = {
{ "update", no_argument, NULL, 'u' },
{ "help", no_argument, NULL, 'h' },
{}
};
bool update = false;
struct trie *trie;
char **files, **f;
int err;
int rc = EXIT_SUCCESS;
for (;;) {
int option;
option = getopt_long(argc, argv, "ch", options, NULL);
if (option == -1)
break;
switch (option) {
case 'u':
update = true;
break;
case 'h':
help();
return EXIT_SUCCESS;
}
}
if (!update) {
help();
return EXIT_SUCCESS;
}
trie = calloc(sizeof(struct trie), 1);
if (!trie) {
rc = EXIT_FAILURE;
goto out;
}
/* string store */
trie->strings = strbuf_new();
if (!trie->strings) {
rc = EXIT_FAILURE;
goto out;
}
/* index */
trie->root = calloc(sizeof(struct trie_node), 1);
if (!trie->root) {
rc = EXIT_FAILURE;
goto out;
}
trie->nodes_count++;
err = conf_files_list_strv(&files, ".hwdb", (const char **)conf_file_dirs);
if (err < 0) {
log_error("failed to enumerate hwdb files: %s\n", strerror(-err));
rc = EXIT_FAILURE;
goto out;
}
STRV_FOREACH(f, files) {
log_debug("reading file '%s'", *f);
import_file(trie, *f);
}
strv_free(files);
strbuf_complete(trie->strings);
log_debug("=== trie in-memory ===\n");
log_debug("nodes: %8zu bytes (%8zu)\n", trie->nodes_count * sizeof(struct trie_node), trie->nodes_count);
log_debug("children arrays: %8zu bytes (%8zu)\n", trie->children_count * sizeof(struct trie_child_entry), trie->children_count);
log_debug("values arrays: %8zu bytes (%8zu)\n", trie->values_count * sizeof(struct trie_value_entry), trie->values_count);
log_debug("strings: %8zu bytes\n", trie->strings->len);
log_debug("strings incoming: %8zu bytes (%8zu)\n", trie->strings->in_len, trie->strings->in_count);
log_debug("strings dedup'ed: %8zu bytes (%8zu)\n", trie->strings->dedup_len, trie->strings->dedup_count);
mkdir_parents(SYSCONFDIR "/udev/hwdb.bin", 0755);
err = trie_store(trie, SYSCONFDIR "/udev/hwdb.bin");
if (err < 0) {
log_error("Failure writing hardware database '%s': %s", SYSCONFDIR "/udev/hwdb.bin", strerror(-err));
rc = EXIT_FAILURE;
}
out:
if (trie->root)
trie_node_cleanup(trie->root);
strbuf_cleanup(trie->strings);
free(trie);
return rc;
}
const struct udevadm_cmd udevadm_hwdb = {
.name = "hwdb",
.cmd = adm_hwdb,
.help = "maintain the hardware database index",
};

View File

@ -56,6 +56,7 @@ static const struct udevadm_cmd *udevadm_cmds[] = {
&udevadm_settle,
&udevadm_control,
&udevadm_monitor,
&udevadm_hwdb,
&udevadm_test,
&udevadm_test_builtin,
&udevadm_version,
@ -133,7 +134,7 @@ int main(int argc, char *argv[])
if (strcmp(udevadm_cmds[i]->name, command) == 0) {
argc -= optind;
argv += optind;
optind = 0;
optind = 1;
rc = run_command(udev, udevadm_cmds[i], argc, argv);
goto out;
}