/* * * Embedded Linux library * * Copyright (C) 2011-2014 Intel Corporation. All rights reserved. * * This library 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. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "util.h" #include "hwdb.h" #include "private.h" static const char trie_sig[8] = { 'K', 'S', 'L', 'P', 'H', 'H', 'R', 'H' }; struct trie_header { uint8_t signature[8]; /* Signature */ uint64_t version; /* Version of creator tool */ uint64_t file_size; /* Size of complete file */ uint64_t header_size; /* Size of header structure */ uint64_t node_size; /* Size of node structure */ uint64_t child_size; /* Size of child structure */ uint64_t entry_size; /* Size of entry structure */ uint64_t root_offset; /* Location of root node structure */ uint64_t nodes_size; /* Size of the nodes section */ uint64_t strings_size; /* Size of the strings section */ /* followed by nodes_size nodes data */ /* followed by strings_size strings data */ } __attribute__ ((packed)); struct trie_node { uint64_t prefix_offset; /* Location of prefix string */ uint8_t child_count; /* Number of child structures */ uint8_t padding[7]; uint64_t entry_count; /* Number of entry structures */ /* followed by child_count child structures */ /* followed by entry_count entry structures */ } __attribute__ ((packed)); struct trie_child { uint8_t c; /* Prefix character of child node */ uint8_t padding[7]; uint64_t child_offset; /* Location of child node structure */ } __attribute__ ((packed)); struct trie_entry { uint64_t key_offset; /* Location of key string */ uint64_t value_offset; /* Location of value string */ } __attribute__ ((packed)); struct l_hwdb { int ref_count; int fd; time_t mtime; size_t size; void *addr; uint64_t root; }; LIB_EXPORT struct l_hwdb *l_hwdb_new(const char *pathname) { struct trie_header *hdr; struct l_hwdb *hwdb; struct stat st; void *addr; size_t size; int fd; if (!pathname) return NULL; fd = open(pathname, O_RDONLY | O_CLOEXEC); if (fd < 0) return NULL; if (fstat(fd, &st) < 0) { close(fd); return NULL; } size = st.st_size; if (size < sizeof(struct trie_header)) { close(fd); return NULL; } addr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) { close(fd); return NULL; } hdr = addr; if (memcmp(hdr->signature, trie_sig, sizeof(trie_sig))) goto failed; if (L_LE64_TO_CPU(hdr->file_size) != size) goto failed; if (L_LE64_TO_CPU(hdr->header_size) != sizeof(struct trie_header)) goto failed; if (L_LE64_TO_CPU(hdr->node_size) != sizeof(struct trie_node)) goto failed; if (L_LE64_TO_CPU(hdr->child_size) != sizeof(struct trie_child)) goto failed; if (L_LE64_TO_CPU(hdr->entry_size) < sizeof(struct trie_entry)) goto failed; if (L_LE64_TO_CPU(hdr->header_size) + L_LE64_TO_CPU(hdr->nodes_size) + L_LE64_TO_CPU(hdr->strings_size) != size) goto failed; hwdb = l_new(struct l_hwdb, 1); hwdb->fd = fd; hwdb->mtime = st.st_mtime; hwdb->size = size; hwdb->addr = addr; hwdb->root = L_LE64_TO_CPU(hdr->root_offset); return l_hwdb_ref(hwdb); failed: munmap(addr, st.st_size); close(fd); return NULL; } LIB_EXPORT struct l_hwdb *l_hwdb_new_default(void) { struct l_hwdb *db = NULL; size_t i; const char * const paths[] = {"/etc/udev/hwdb.bin", "/usr/lib/udev/hwdb.bin", "/lib/udev/hwdb.bin"}; for (i = 0; !db && i < L_ARRAY_SIZE(paths); i++) db = l_hwdb_new(paths[i]); return db; } LIB_EXPORT struct l_hwdb *l_hwdb_ref(struct l_hwdb *hwdb) { if (!hwdb) return NULL; __atomic_fetch_add(&hwdb->ref_count, 1, __ATOMIC_SEQ_CST); return hwdb; } LIB_EXPORT void l_hwdb_unref(struct l_hwdb *hwdb) { if (!hwdb) return; if (__atomic_sub_fetch(&hwdb->ref_count, 1, __ATOMIC_SEQ_CST)) return; munmap(hwdb->addr, hwdb->size); close(hwdb->fd); l_free(hwdb); } static void trie_fnmatch(const void *addr, uint64_t offset, const char *prefix, const char *string, struct l_hwdb_entry **entries) { const struct trie_node *node = addr + offset; const void *addr_ptr = addr + offset + sizeof(*node); const char *prefix_str = addr + L_LE64_TO_CPU(node->prefix_offset); uint64_t child_count = L_LE64_TO_CPU(node->child_count); uint64_t entry_count = L_LE64_TO_CPU(node->entry_count); uint64_t i; size_t scratch_len; char *scratch_buf; scratch_len = strlen(prefix) + strlen(prefix_str); scratch_buf = alloca(scratch_len + 2); sprintf(scratch_buf, "%s%s", prefix, prefix_str); scratch_buf[scratch_len + 1] = '\0'; /* * Only incur the cost of this fnmatch() if there are children * to visit. In practice, nodes have either entries or children * so fnmatch() will only be called once per node. */ if (child_count) { scratch_buf[scratch_len] = '*'; if (fnmatch(scratch_buf, string, 0) == FNM_NOMATCH) child_count = 0; } for (i = 0; i < child_count; i++) { const struct trie_child *child = addr_ptr; scratch_buf[scratch_len] = child->c; trie_fnmatch(addr, L_LE64_TO_CPU(child->child_offset), scratch_buf, string, entries); addr_ptr += sizeof(*child); } if (!entry_count) return; scratch_buf[scratch_len] = '\0'; if (fnmatch(scratch_buf, string, 0)) return; for (i = 0; i < entry_count; i++) { const struct trie_entry *entry = addr_ptr; const char *key_str = addr + L_LE64_TO_CPU(entry->key_offset); const char *val_str = addr + L_LE64_TO_CPU(entry->value_offset); struct l_hwdb_entry *result; if (key_str[0] == ' ') { result = l_new(struct l_hwdb_entry, 1); result->key = key_str + 1; result->value = val_str; result->next = (*entries); *entries = result; } addr_ptr += sizeof(*entry); } } LIB_EXPORT struct l_hwdb_entry *l_hwdb_lookup(struct l_hwdb *hwdb, const char *format, ...) { struct l_hwdb_entry *entries = NULL; va_list args; va_start(args, format); entries = l_hwdb_lookup_valist(hwdb, format, args); va_end(args); return entries; } LIB_EXPORT struct l_hwdb_entry *l_hwdb_lookup_valist(struct l_hwdb *hwdb, const char *format, va_list args) { struct l_hwdb_entry *entries = NULL; char *modalias; int len; if (!hwdb || !format) return NULL; len = vasprintf(&modalias, format, args); if (len < 0) return NULL; trie_fnmatch(hwdb->addr, hwdb->root, "", modalias, &entries); free(modalias); return entries; } LIB_EXPORT void l_hwdb_lookup_free(struct l_hwdb_entry *entries) { while (entries) { struct l_hwdb_entry *entry = entries; entries = entries->next; l_free(entry); } } static void foreach_node(const void *addr, uint64_t offset, const char *prefix, l_hwdb_foreach_func_t func, void *user_data) { const struct trie_node *node = addr + offset; const void *addr_ptr = addr + offset + sizeof(*node); const char *prefix_str = addr + L_LE64_TO_CPU(node->prefix_offset); uint64_t child_count = L_LE64_TO_CPU(node->child_count); uint64_t entry_count = L_LE64_TO_CPU(node->entry_count); uint64_t i; size_t scratch_len; char *scratch_buf; struct l_hwdb_entry *entries = NULL; scratch_len = strlen(prefix) + strlen(prefix_str); scratch_buf = alloca(scratch_len + 2); sprintf(scratch_buf, "%s%s", prefix, prefix_str); scratch_buf[scratch_len + 1] = '\0'; for (i = 0; i < child_count; i++) { const struct trie_child *child = addr_ptr; scratch_buf[scratch_len] = child->c; foreach_node(addr, L_LE64_TO_CPU(child->child_offset), scratch_buf, func, user_data); addr_ptr += sizeof(*child); } if (!entry_count) return; scratch_buf[scratch_len] = '\0'; for (i = 0; i < entry_count; i++) { const struct trie_entry *entry = addr_ptr; const char *key_str = addr + L_LE64_TO_CPU(entry->key_offset); const char *val_str = addr + L_LE64_TO_CPU(entry->value_offset); struct l_hwdb_entry *result; if (key_str[0] == ' ') { result = l_new(struct l_hwdb_entry, 1); result->key = key_str + 1; result->value = val_str; result->next = entries; entries = result; } addr_ptr += sizeof(*entry); } func(scratch_buf, entries, user_data); l_hwdb_lookup_free(entries); } LIB_EXPORT bool l_hwdb_foreach(struct l_hwdb *hwdb, l_hwdb_foreach_func_t func, void *user_data) { if (!hwdb || !func) return false; foreach_node(hwdb->addr, hwdb->root, "", func, user_data); return true; }