LCOV - code coverage report
Current view: top level - libsystemd/sd-hwdb - sd-hwdb.c (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 169 239 70.7 %
Date: 2019-08-22 15:41:25 Functions: 21 24 87.5 %

          Line data    Source code
       1             : /* SPDX-License-Identifier: LGPL-2.1+ */
       2             : /***
       3             :   Copyright © 2008 Alan Jenkins <alan.christopher.jenkins@googlemail.com>
       4             : ***/
       5             : 
       6             : #include <errno.h>
       7             : #include <fnmatch.h>
       8             : #include <inttypes.h>
       9             : #include <stdio.h>
      10             : #include <stdlib.h>
      11             : #include <string.h>
      12             : #include <sys/mman.h>
      13             : #include <sys/stat.h>
      14             : 
      15             : #include "sd-hwdb.h"
      16             : 
      17             : #include "alloc-util.h"
      18             : #include "fd-util.h"
      19             : #include "hashmap.h"
      20             : #include "hwdb-internal.h"
      21             : #include "hwdb-util.h"
      22             : #include "nulstr-util.h"
      23             : #include "string-util.h"
      24             : #include "time-util.h"
      25             : 
      26             : struct sd_hwdb {
      27             :         unsigned n_ref;
      28             : 
      29             :         FILE *f;
      30             :         struct stat st;
      31             :         union {
      32             :                 struct trie_header_f *head;
      33             :                 const char *map;
      34             :         };
      35             : 
      36             :         OrderedHashmap *properties;
      37             :         Iterator properties_iterator;
      38             :         bool properties_modified;
      39             : };
      40             : 
      41             : struct linebuf {
      42             :         char bytes[LINE_MAX];
      43             :         size_t size;
      44             :         size_t len;
      45             : };
      46             : 
      47           4 : static void linebuf_init(struct linebuf *buf) {
      48           4 :         buf->size = 0;
      49           4 :         buf->len = 0;
      50           4 : }
      51             : 
      52         449 : static const char *linebuf_get(struct linebuf *buf) {
      53         449 :         if (buf->len + 1 >= sizeof(buf->bytes))
      54           0 :                 return NULL;
      55         449 :         buf->bytes[buf->len] = '\0';
      56         449 :         return buf->bytes;
      57             : }
      58             : 
      59         695 : static bool linebuf_add(struct linebuf *buf, const char *s, size_t len) {
      60         695 :         if (buf->len + len >= sizeof(buf->bytes))
      61           0 :                 return false;
      62         695 :         memcpy(buf->bytes + buf->len, s, len);
      63         695 :         buf->len += len;
      64         695 :         return true;
      65             : }
      66             : 
      67         692 : static bool linebuf_add_char(struct linebuf *buf, char c) {
      68         692 :         if (buf->len + 1 >= sizeof(buf->bytes))
      69           0 :                 return false;
      70         692 :         buf->bytes[buf->len++] = c;
      71         692 :         return true;
      72             : }
      73             : 
      74        1387 : static void linebuf_rem(struct linebuf *buf, size_t count) {
      75        1387 :         assert(buf->len >= count);
      76        1387 :         buf->len -= count;
      77        1387 : }
      78             : 
      79         692 : static void linebuf_rem_char(struct linebuf *buf) {
      80         692 :         linebuf_rem(buf, 1);
      81         692 : }
      82             : 
      83         690 : static const struct trie_child_entry_f *trie_node_child(sd_hwdb *hwdb, const struct trie_node_f *node, size_t idx) {
      84         690 :         const char *base = (const char *)node;
      85             : 
      86         690 :         base += le64toh(hwdb->head->node_size);
      87         690 :         base += idx * le64toh(hwdb->head->child_entry_size);
      88         690 :         return (const struct trie_child_entry_f *)base;
      89             : }
      90             : 
      91          60 : static const struct trie_value_entry_f *trie_node_value(sd_hwdb *hwdb, const struct trie_node_f *node, size_t idx) {
      92          60 :         const char *base = (const char *)node;
      93             : 
      94          60 :         base += le64toh(hwdb->head->node_size);
      95          60 :         base += node->children_count * le64toh(hwdb->head->child_entry_size);
      96          60 :         base += idx * le64toh(hwdb->head->value_entry_size);
      97          60 :         return (const struct trie_value_entry_f *)base;
      98             : }
      99             : 
     100         707 : static const struct trie_node_f *trie_node_from_off(sd_hwdb *hwdb, le64_t off) {
     101         707 :         return (const struct trie_node_f *)(hwdb->map + le64toh(off));
     102             : }
     103             : 
     104         871 : static const char *trie_string(sd_hwdb *hwdb, le64_t off) {
     105         871 :         return hwdb->map + le64toh(off);
     106             : }
     107             : 
     108         154 : static int trie_children_cmp_f(const void *v1, const void *v2) {
     109         154 :         const struct trie_child_entry_f *n1 = v1;
     110         154 :         const struct trie_child_entry_f *n2 = v2;
     111             : 
     112         154 :         return n1->c - n2->c;
     113             : }
     114             : 
     115          48 : static const struct trie_node_f *node_lookup_f(sd_hwdb *hwdb, const struct trie_node_f *node, uint8_t c) {
     116             :         struct trie_child_entry_f *child;
     117             :         struct trie_child_entry_f search;
     118             : 
     119          48 :         search.c = c;
     120          48 :         child = bsearch(&search, (const char *)node + le64toh(hwdb->head->node_size), node->children_count,
     121          48 :                         le64toh(hwdb->head->child_entry_size), trie_children_cmp_f);
     122          48 :         if (child)
     123          13 :                 return trie_node_from_off(hwdb, child->child_off);
     124          35 :         return NULL;
     125             : }
     126             : 
     127          60 : static int hwdb_add_property(sd_hwdb *hwdb, const struct trie_value_entry_f *entry) {
     128             :         const char *key;
     129             :         int r;
     130             : 
     131          60 :         assert(hwdb);
     132             : 
     133          60 :         key = trie_string(hwdb, entry->key_off);
     134             : 
     135             :         /*
     136             :          * Silently ignore all properties which do not start with a
     137             :          * space; future extensions might use additional prefixes.
     138             :          */
     139          60 :         if (key[0] != ' ')
     140           0 :                 return 0;
     141             : 
     142          60 :         key++;
     143             : 
     144          60 :         if (le64toh(hwdb->head->value_entry_size) >= sizeof(struct trie_value_entry2_f)) {
     145             :                 const struct trie_value_entry2_f *old, *entry2;
     146             : 
     147          60 :                 entry2 = (const struct trie_value_entry2_f *)entry;
     148          60 :                 old = ordered_hashmap_get(hwdb->properties, key);
     149          60 :                 if (old) {
     150             :                         /* On duplicates, we order by filename priority and line-number.
     151             :                          *
     152             :                          * v2 of the format had 64 bits for the line number.
     153             :                          * v3 reuses top 32 bits of line_number to store the priority.
     154             :                          * We check the top bits — if they are zero we have v2 format.
     155             :                          * This means that v2 clients will print wrong line numbers with
     156             :                          * v3 data.
     157             :                          *
     158             :                          * For v3 data: we compare the priority (of the source file)
     159             :                          * and the line number.
     160             :                          *
     161             :                          * For v2 data: we rely on the fact that the filenames in the hwdb
     162             :                          * are added in the order of priority (higher later), because they
     163             :                          * are *processed* in the order of priority. So we compare the
     164             :                          * indices to determine which file had higher priority. Comparing
     165             :                          * the strings alphabetically would be useless, because those are
     166             :                          * full paths, and e.g. /usr/lib would sort after /etc, even
     167             :                          * though it has lower priority. This is not reliable because of
     168             :                          * suffix compression, but should work for the most common case of
     169             :                          * /usr/lib/udev/hwbd.d and /etc/udev/hwdb.d, and is better than
     170             :                          * not doing the comparison at all.
     171             :                          */
     172             :                         bool lower;
     173             : 
     174           0 :                         if (entry2->file_priority == 0)
     175           0 :                                 lower = entry2->filename_off < old->filename_off ||
     176           0 :                                         (entry2->filename_off == old->filename_off && entry2->line_number < old->line_number);
     177             :                         else
     178           0 :                                 lower = entry2->file_priority < old->file_priority ||
     179           0 :                                         (entry2->file_priority == old->file_priority && entry2->line_number < old->line_number);
     180           0 :                         if (lower)
     181           0 :                                 return 0;
     182             :                 }
     183             :         }
     184             : 
     185          60 :         r = ordered_hashmap_ensure_allocated(&hwdb->properties, &string_hash_ops);
     186          60 :         if (r < 0)
     187           0 :                 return r;
     188             : 
     189          60 :         r = ordered_hashmap_replace(hwdb->properties, key, (void *)entry);
     190          60 :         if (r < 0)
     191           0 :                 return r;
     192             : 
     193          60 :         hwdb->properties_modified = true;
     194             : 
     195          60 :         return 0;
     196             : }
     197             : 
     198         695 : static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t p,
     199             :                           struct linebuf *buf, const char *search) {
     200             :         size_t len;
     201             :         size_t i;
     202             :         const char *prefix;
     203             :         int err;
     204             : 
     205         695 :         prefix = trie_string(hwdb, node->prefix_off);
     206         695 :         len = strlen(prefix + p);
     207         695 :         linebuf_add(buf, prefix + p, len);
     208             : 
     209        1385 :         for (i = 0; i < node->children_count; i++) {
     210         690 :                 const struct trie_child_entry_f *child = trie_node_child(hwdb, node, i);
     211             : 
     212         690 :                 linebuf_add_char(buf, child->c);
     213         690 :                 err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search);
     214         690 :                 if (err < 0)
     215           0 :                         return err;
     216         690 :                 linebuf_rem_char(buf);
     217             :         }
     218             : 
     219         695 :         if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0)
     220          64 :                 for (i = 0; i < le64toh(node->values_count); i++) {
     221          60 :                         err = hwdb_add_property(hwdb, trie_node_value(hwdb, node, i));
     222          60 :                         if (err < 0)
     223           0 :                                 return err;
     224             :                 }
     225             : 
     226         695 :         linebuf_rem(buf, len);
     227         695 :         return 0;
     228             : }
     229             : 
     230           4 : static int trie_search_f(sd_hwdb *hwdb, const char *search) {
     231             :         struct linebuf buf;
     232             :         const struct trie_node_f *node;
     233           4 :         size_t i = 0;
     234             :         int err;
     235             : 
     236           4 :         linebuf_init(&buf);
     237             : 
     238           4 :         node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off);
     239          16 :         while (node) {
     240             :                 const struct trie_node_f *child;
     241          15 :                 size_t p = 0;
     242             : 
     243          15 :                 if (node->prefix_off) {
     244             :                         char c;
     245             : 
     246          56 :                         for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) {
     247          44 :                                 if (IN_SET(c, '*', '?', '['))
     248           3 :                                         return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p);
     249          41 :                                 if (c != search[i + p])
     250           0 :                                         return 0;
     251             :                         }
     252          12 :                         i += p;
     253             :                 }
     254             : 
     255          12 :                 child = node_lookup_f(hwdb, node, '*');
     256          12 :                 if (child) {
     257           2 :                         linebuf_add_char(&buf, '*');
     258           2 :                         err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
     259           2 :                         if (err < 0)
     260           0 :                                 return err;
     261           2 :                         linebuf_rem_char(&buf);
     262             :                 }
     263             : 
     264          12 :                 child = node_lookup_f(hwdb, node, '?');
     265          12 :                 if (child) {
     266           0 :                         linebuf_add_char(&buf, '?');
     267           0 :                         err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
     268           0 :                         if (err < 0)
     269           0 :                                 return err;
     270           0 :                         linebuf_rem_char(&buf);
     271             :                 }
     272             : 
     273          12 :                 child = node_lookup_f(hwdb, node, '[');
     274          12 :                 if (child) {
     275           0 :                         linebuf_add_char(&buf, '[');
     276           0 :                         err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
     277           0 :                         if (err < 0)
     278           0 :                                 return err;
     279           0 :                         linebuf_rem_char(&buf);
     280             :                 }
     281             : 
     282          12 :                 if (search[i] == '\0') {
     283             :                         size_t n;
     284             : 
     285           0 :                         for (n = 0; n < le64toh(node->values_count); n++) {
     286           0 :                                 err = hwdb_add_property(hwdb, trie_node_value(hwdb, node, n));
     287           0 :                                 if (err < 0)
     288           0 :                                         return err;
     289             :                         }
     290           0 :                         return 0;
     291             :                 }
     292             : 
     293          12 :                 child = node_lookup_f(hwdb, node, search[i]);
     294          12 :                 node = child;
     295          12 :                 i++;
     296             :         }
     297           1 :         return 0;
     298             : }
     299             : 
     300             : static const char hwdb_bin_paths[] =
     301             :         "/etc/systemd/hwdb/hwdb.bin\0"
     302             :         "/etc/udev/hwdb.bin\0"
     303             :         "/usr/lib/systemd/hwdb/hwdb.bin\0"
     304             : #if HAVE_SPLIT_USR
     305             :         "/lib/systemd/hwdb/hwdb.bin\0"
     306             : #endif
     307             :         UDEVLIBEXECDIR "/hwdb.bin\0";
     308             : 
     309           3 : _public_ int sd_hwdb_new(sd_hwdb **ret) {
     310           3 :         _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
     311             :         const char *hwdb_bin_path;
     312           3 :         const char sig[] = HWDB_SIG;
     313             : 
     314           3 :         assert_return(ret, -EINVAL);
     315             : 
     316           3 :         hwdb = new0(sd_hwdb, 1);
     317           3 :         if (!hwdb)
     318           0 :                 return -ENOMEM;
     319             : 
     320           3 :         hwdb->n_ref = 1;
     321             : 
     322             :         /* find hwdb.bin in hwdb_bin_paths */
     323           6 :         NULSTR_FOREACH(hwdb_bin_path, hwdb_bin_paths) {
     324           6 :                 log_debug("Trying to open \"%s\"...", hwdb_bin_path);
     325           6 :                 hwdb->f = fopen(hwdb_bin_path, "re");
     326           6 :                 if (hwdb->f)
     327           3 :                         break;
     328           3 :                 if (errno != ENOENT)
     329           0 :                         return log_debug_errno(errno, "Failed to open %s: %m", hwdb_bin_path);
     330             :         }
     331             : 
     332           3 :         if (!hwdb->f)
     333           0 :                 return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
     334             :                                        "hwdb.bin does not exist, please run 'systemd-hwdb update'");
     335             : 
     336           3 :         if (fstat(fileno(hwdb->f), &hwdb->st) < 0)
     337           0 :                 return log_debug_errno(errno, "Failed to stat %s: %m", hwdb_bin_path);
     338           3 :         if (hwdb->st.st_size < (off_t) offsetof(struct trie_header_f, strings_len) + 8)
     339           0 :                 return log_debug_errno(SYNTHETIC_ERRNO(EIO),
     340             :                                        "File %s is too short: %m", hwdb_bin_path);
     341             : 
     342           3 :         hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0);
     343           3 :         if (hwdb->map == MAP_FAILED)
     344           0 :                 return log_debug_errno(errno, "Failed to map %s: %m", hwdb_bin_path);
     345             : 
     346           6 :         if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 ||
     347           3 :             (size_t) hwdb->st.st_size != le64toh(hwdb->head->file_size)) {
     348           0 :                 log_debug("Failed to recognize the format of %s", hwdb_bin_path);
     349           0 :                 return -EINVAL;
     350             :         }
     351             : 
     352           3 :         log_debug("=== trie on-disk ===");
     353           3 :         log_debug("tool version:          %"PRIu64, le64toh(hwdb->head->tool_version));
     354           3 :         log_debug("file size:        %8"PRIi64" bytes", hwdb->st.st_size);
     355           3 :         log_debug("header size       %8"PRIu64" bytes", le64toh(hwdb->head->header_size));
     356           3 :         log_debug("strings           %8"PRIu64" bytes", le64toh(hwdb->head->strings_len));
     357           3 :         log_debug("nodes             %8"PRIu64" bytes", le64toh(hwdb->head->nodes_len));
     358             : 
     359           3 :         *ret = TAKE_PTR(hwdb);
     360             : 
     361           3 :         return 0;
     362             : }
     363             : 
     364           3 : static sd_hwdb *hwdb_free(sd_hwdb *hwdb) {
     365           3 :         assert(hwdb);
     366             : 
     367           3 :         if (hwdb->map)
     368           3 :                 munmap((void *)hwdb->map, hwdb->st.st_size);
     369           3 :         safe_fclose(hwdb->f);
     370           3 :         ordered_hashmap_free(hwdb->properties);
     371           3 :         return mfree(hwdb);
     372             : }
     373             : 
     374           3 : DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_hwdb, sd_hwdb, hwdb_free)
     375             : 
     376           0 : bool hwdb_validate(sd_hwdb *hwdb) {
     377           0 :         bool found = false;
     378             :         const char* p;
     379             :         struct stat st;
     380             : 
     381           0 :         if (!hwdb)
     382           0 :                 return false;
     383           0 :         if (!hwdb->f)
     384           0 :                 return false;
     385             : 
     386             :         /* if hwdb.bin doesn't exist anywhere, we need to update */
     387           0 :         NULSTR_FOREACH(p, hwdb_bin_paths)
     388           0 :                 if (stat(p, &st) >= 0) {
     389           0 :                         found = true;
     390           0 :                         break;
     391             :                 }
     392           0 :         if (!found)
     393           0 :                 return true;
     394             : 
     395           0 :         if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim))
     396           0 :                 return true;
     397           0 :         return false;
     398             : }
     399             : 
     400           4 : static int properties_prepare(sd_hwdb *hwdb, const char *modalias) {
     401           4 :         assert(hwdb);
     402           4 :         assert(modalias);
     403             : 
     404           4 :         ordered_hashmap_clear(hwdb->properties);
     405           4 :         hwdb->properties_modified = true;
     406             : 
     407           4 :         return trie_search_f(hwdb, modalias);
     408             : }
     409             : 
     410           0 : _public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **_value) {
     411             :         const struct trie_value_entry_f *entry;
     412             :         int r;
     413             : 
     414           0 :         assert_return(hwdb, -EINVAL);
     415           0 :         assert_return(hwdb->f, -EINVAL);
     416           0 :         assert_return(modalias, -EINVAL);
     417           0 :         assert_return(_value, -EINVAL);
     418             : 
     419           0 :         r = properties_prepare(hwdb, modalias);
     420           0 :         if (r < 0)
     421           0 :                 return r;
     422             : 
     423           0 :         entry = ordered_hashmap_get(hwdb->properties, key);
     424           0 :         if (!entry)
     425           0 :                 return -ENOENT;
     426             : 
     427           0 :         *_value = trie_string(hwdb, entry->value_off);
     428             : 
     429           0 :         return 0;
     430             : }
     431             : 
     432           4 : _public_ int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias) {
     433             :         int r;
     434             : 
     435           4 :         assert_return(hwdb, -EINVAL);
     436           4 :         assert_return(hwdb->f, -EINVAL);
     437           4 :         assert_return(modalias, -EINVAL);
     438             : 
     439           4 :         r = properties_prepare(hwdb, modalias);
     440           4 :         if (r < 0)
     441           0 :                 return r;
     442             : 
     443           4 :         hwdb->properties_modified = false;
     444           4 :         hwdb->properties_iterator = ITERATOR_FIRST;
     445             : 
     446           4 :         return 0;
     447             : }
     448             : 
     449          66 : _public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value) {
     450             :         const struct trie_value_entry_f *entry;
     451             :         const void *k;
     452             : 
     453          66 :         assert_return(hwdb, -EINVAL);
     454          66 :         assert_return(key, -EINVAL);
     455          65 :         assert_return(value, -EINVAL);
     456             : 
     457          64 :         if (hwdb->properties_modified)
     458           0 :                 return -EAGAIN;
     459             : 
     460          64 :         if (!ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, (void **)&entry, &k))
     461           4 :                 return 0;
     462             : 
     463          60 :         *key = k;
     464          60 :         *value = trie_string(hwdb, entry->value_off);
     465             : 
     466          60 :         return 1;
     467             : }

Generated by: LCOV version 1.14