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