blob: c3f27cfc6c554761ffe4277be9f4cbe8b6f2c94a [file] [log] [blame]
/*
*
* 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 <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <alloca.h>
#include <fnmatch.h>
#include <sys/stat.h>
#include <sys/mman.h>
#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;
}