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 : }
|