Branch data 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 : 16 : static void linebuf_init(struct linebuf *buf) {
48 : 16 : buf->size = 0;
49 : 16 : buf->len = 0;
50 : 16 : }
51 : :
52 : 1796 : static const char *linebuf_get(struct linebuf *buf) {
53 [ - + ]: 1796 : if (buf->len + 1 >= sizeof(buf->bytes))
54 : 0 : return NULL;
55 : 1796 : buf->bytes[buf->len] = '\0';
56 : 1796 : return buf->bytes;
57 : : }
58 : :
59 : 2780 : static bool linebuf_add(struct linebuf *buf, const char *s, size_t len) {
60 [ - + ]: 2780 : if (buf->len + len >= sizeof(buf->bytes))
61 : 0 : return false;
62 : 2780 : memcpy(buf->bytes + buf->len, s, len);
63 : 2780 : buf->len += len;
64 : 2780 : return true;
65 : : }
66 : :
67 : 2768 : static bool linebuf_add_char(struct linebuf *buf, char c) {
68 [ - + ]: 2768 : if (buf->len + 1 >= sizeof(buf->bytes))
69 : 0 : return false;
70 : 2768 : buf->bytes[buf->len++] = c;
71 : 2768 : return true;
72 : : }
73 : :
74 : 5548 : static void linebuf_rem(struct linebuf *buf, size_t count) {
75 [ - + ]: 5548 : assert(buf->len >= count);
76 : 5548 : buf->len -= count;
77 : 5548 : }
78 : :
79 : 2768 : static void linebuf_rem_char(struct linebuf *buf) {
80 : 2768 : linebuf_rem(buf, 1);
81 : 2768 : }
82 : :
83 : 2760 : static const struct trie_child_entry_f *trie_node_child(sd_hwdb *hwdb, const struct trie_node_f *node, size_t idx) {
84 : 2760 : const char *base = (const char *)node;
85 : :
86 : 2760 : base += le64toh(hwdb->head->node_size);
87 : 2760 : base += idx * le64toh(hwdb->head->child_entry_size);
88 : 2760 : return (const struct trie_child_entry_f *)base;
89 : : }
90 : :
91 : 240 : static const struct trie_value_entry_f *trie_node_value(sd_hwdb *hwdb, const struct trie_node_f *node, size_t idx) {
92 : 240 : const char *base = (const char *)node;
93 : :
94 : 240 : base += le64toh(hwdb->head->node_size);
95 : 240 : base += node->children_count * le64toh(hwdb->head->child_entry_size);
96 : 240 : base += idx * le64toh(hwdb->head->value_entry_size);
97 : 240 : return (const struct trie_value_entry_f *)base;
98 : : }
99 : :
100 : 2828 : static const struct trie_node_f *trie_node_from_off(sd_hwdb *hwdb, le64_t off) {
101 : 2828 : return (const struct trie_node_f *)(hwdb->map + le64toh(off));
102 : : }
103 : :
104 : 3484 : static const char *trie_string(sd_hwdb *hwdb, le64_t off) {
105 : 3484 : return hwdb->map + le64toh(off);
106 : : }
107 : :
108 : 616 : static int trie_children_cmp_f(const void *v1, const void *v2) {
109 : 616 : const struct trie_child_entry_f *n1 = v1;
110 : 616 : const struct trie_child_entry_f *n2 = v2;
111 : :
112 : 616 : return n1->c - n2->c;
113 : : }
114 : :
115 : 192 : 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 : 192 : search.c = c;
120 : 192 : child = bsearch(&search, (const char *)node + le64toh(hwdb->head->node_size), node->children_count,
121 : 192 : le64toh(hwdb->head->child_entry_size), trie_children_cmp_f);
122 [ + + ]: 192 : if (child)
123 : 52 : return trie_node_from_off(hwdb, child->child_off);
124 : 140 : return NULL;
125 : : }
126 : :
127 : 240 : static int hwdb_add_property(sd_hwdb *hwdb, const struct trie_value_entry_f *entry) {
128 : : const char *key;
129 : : int r;
130 : :
131 [ - + ]: 240 : assert(hwdb);
132 : :
133 : 240 : 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 [ - + ]: 240 : if (key[0] != ' ')
140 : 0 : return 0;
141 : :
142 : 240 : key++;
143 : :
144 [ + - ]: 240 : if (le64toh(hwdb->head->value_entry_size) >= sizeof(struct trie_value_entry2_f)) {
145 : : const struct trie_value_entry2_f *old, *entry2;
146 : :
147 : 240 : entry2 = (const struct trie_value_entry2_f *)entry;
148 : 240 : old = ordered_hashmap_get(hwdb->properties, key);
149 [ - + ]: 240 : 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 : 240 : r = ordered_hashmap_ensure_allocated(&hwdb->properties, &string_hash_ops);
186 [ - + ]: 240 : if (r < 0)
187 : 0 : return r;
188 : :
189 : 240 : r = ordered_hashmap_replace(hwdb->properties, key, (void *)entry);
190 [ - + ]: 240 : if (r < 0)
191 : 0 : return r;
192 : :
193 : 240 : hwdb->properties_modified = true;
194 : :
195 : 240 : return 0;
196 : : }
197 : :
198 : 2780 : 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 : 2780 : prefix = trie_string(hwdb, node->prefix_off);
206 : 2780 : len = strlen(prefix + p);
207 : 2780 : linebuf_add(buf, prefix + p, len);
208 : :
209 [ + + ]: 5540 : for (i = 0; i < node->children_count; i++) {
210 : 2760 : const struct trie_child_entry_f *child = trie_node_child(hwdb, node, i);
211 : :
212 : 2760 : linebuf_add_char(buf, child->c);
213 : 2760 : err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search);
214 [ - + ]: 2760 : if (err < 0)
215 : 0 : return err;
216 : 2760 : linebuf_rem_char(buf);
217 : : }
218 : :
219 [ + + + + ]: 2780 : if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0)
220 [ + + ]: 256 : for (i = 0; i < le64toh(node->values_count); i++) {
221 : 240 : err = hwdb_add_property(hwdb, trie_node_value(hwdb, node, i));
222 [ - + ]: 240 : if (err < 0)
223 : 0 : return err;
224 : : }
225 : :
226 : 2780 : linebuf_rem(buf, len);
227 : 2780 : return 0;
228 : : }
229 : :
230 : 16 : static int trie_search_f(sd_hwdb *hwdb, const char *search) {
231 : : struct linebuf buf;
232 : : const struct trie_node_f *node;
233 : 16 : size_t i = 0;
234 : : int err;
235 : :
236 : 16 : linebuf_init(&buf);
237 : :
238 : 16 : node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off);
239 [ + + ]: 64 : while (node) {
240 : : const struct trie_node_f *child;
241 : 60 : size_t p = 0;
242 : :
243 [ + - ]: 60 : if (node->prefix_off) {
244 : : char c;
245 : :
246 [ + + ]: 224 : for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) {
247 [ + + + + ]: 176 : if (IN_SET(c, '*', '?', '['))
248 : 12 : return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p);
249 [ - + ]: 164 : if (c != search[i + p])
250 : 0 : return 0;
251 : : }
252 : 48 : i += p;
253 : : }
254 : :
255 : 48 : child = node_lookup_f(hwdb, node, '*');
256 [ + + ]: 48 : if (child) {
257 : 8 : linebuf_add_char(&buf, '*');
258 : 8 : err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
259 [ - + ]: 8 : if (err < 0)
260 : 0 : return err;
261 : 8 : linebuf_rem_char(&buf);
262 : : }
263 : :
264 : 48 : child = node_lookup_f(hwdb, node, '?');
265 [ - + ]: 48 : 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 : 48 : child = node_lookup_f(hwdb, node, '[');
274 [ - + ]: 48 : 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 [ - + ]: 48 : 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 : 48 : child = node_lookup_f(hwdb, node, search[i]);
294 : 48 : node = child;
295 : 48 : i++;
296 : : }
297 : 4 : 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 : 12 : _public_ int sd_hwdb_new(sd_hwdb **ret) {
310 : 12 : _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
311 : : const char *hwdb_bin_path;
312 : 12 : const char sig[] = HWDB_SIG;
313 : :
314 [ - + - + ]: 12 : assert_return(ret, -EINVAL);
315 : :
316 : 12 : hwdb = new0(sd_hwdb, 1);
317 [ - + ]: 12 : if (!hwdb)
318 : 0 : return -ENOMEM;
319 : :
320 : 12 : hwdb->n_ref = 1;
321 : :
322 : : /* find hwdb.bin in hwdb_bin_paths */
323 [ + - + - ]: 24 : NULSTR_FOREACH(hwdb_bin_path, hwdb_bin_paths) {
324 [ + + ]: 24 : log_debug("Trying to open \"%s\"...", hwdb_bin_path);
325 : 24 : hwdb->f = fopen(hwdb_bin_path, "re");
326 [ + + ]: 24 : if (hwdb->f)
327 : 12 : break;
328 [ - + ]: 12 : if (errno != ENOENT)
329 [ # # ]: 0 : return log_debug_errno(errno, "Failed to open %s: %m", hwdb_bin_path);
330 : : }
331 : :
332 [ - + ]: 12 : 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 [ - + ]: 12 : if (fstat(fileno(hwdb->f), &hwdb->st) < 0)
337 [ # # ]: 0 : return log_debug_errno(errno, "Failed to stat %s: %m", hwdb_bin_path);
338 [ - + ]: 12 : 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 : 12 : hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0);
343 [ - + ]: 12 : if (hwdb->map == MAP_FAILED)
344 [ # # ]: 0 : return log_debug_errno(errno, "Failed to map %s: %m", hwdb_bin_path);
345 : :
346 [ + - - + ]: 24 : if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 ||
347 : 12 : (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 [ + + ]: 12 : log_debug("=== trie on-disk ===");
353 [ + + ]: 12 : log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version));
354 [ + + ]: 12 : log_debug("file size: %8"PRIi64" bytes", hwdb->st.st_size);
355 [ + + ]: 12 : log_debug("header size %8"PRIu64" bytes", le64toh(hwdb->head->header_size));
356 [ + + ]: 12 : log_debug("strings %8"PRIu64" bytes", le64toh(hwdb->head->strings_len));
357 [ + + ]: 12 : log_debug("nodes %8"PRIu64" bytes", le64toh(hwdb->head->nodes_len));
358 : :
359 : 12 : *ret = TAKE_PTR(hwdb);
360 : :
361 : 12 : return 0;
362 : : }
363 : :
364 : 12 : static sd_hwdb *hwdb_free(sd_hwdb *hwdb) {
365 [ - + ]: 12 : assert(hwdb);
366 : :
367 [ + - ]: 12 : if (hwdb->map)
368 : 12 : munmap((void *)hwdb->map, hwdb->st.st_size);
369 : 12 : safe_fclose(hwdb->f);
370 : 12 : ordered_hashmap_free(hwdb->properties);
371 : 12 : return mfree(hwdb);
372 : : }
373 : :
374 [ - + - + : 12 : 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 : 16 : static int properties_prepare(sd_hwdb *hwdb, const char *modalias) {
401 [ - + ]: 16 : assert(hwdb);
402 [ - + ]: 16 : assert(modalias);
403 : :
404 : 16 : ordered_hashmap_clear(hwdb->properties);
405 : 16 : hwdb->properties_modified = true;
406 : :
407 : 16 : 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 : 16 : _public_ int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias) {
433 : : int r;
434 : :
435 [ - + - + ]: 16 : assert_return(hwdb, -EINVAL);
436 [ - + - + ]: 16 : assert_return(hwdb->f, -EINVAL);
437 [ - + - + ]: 16 : assert_return(modalias, -EINVAL);
438 : :
439 : 16 : r = properties_prepare(hwdb, modalias);
440 [ - + ]: 16 : if (r < 0)
441 : 0 : return r;
442 : :
443 : 16 : hwdb->properties_modified = false;
444 : 16 : hwdb->properties_iterator = ITERATOR_FIRST;
445 : :
446 : 16 : return 0;
447 : : }
448 : :
449 : 264 : _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 [ - + - + ]: 264 : assert_return(hwdb, -EINVAL);
454 [ + + + + ]: 264 : assert_return(key, -EINVAL);
455 [ + + + + ]: 260 : assert_return(value, -EINVAL);
456 : :
457 [ - + ]: 256 : if (hwdb->properties_modified)
458 : 0 : return -EAGAIN;
459 : :
460 [ + + ]: 256 : if (!ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, (void **)&entry, &k))
461 : 16 : return 0;
462 : :
463 : 240 : *key = k;
464 : 240 : *value = trie_string(hwdb, entry->value_off);
465 : :
466 : 240 : return 1;
467 : : }
|