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