]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd/sd-hwdb/sd-hwdb.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / libsystemd / sd-hwdb / sd-hwdb.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
23fbe14f
TG
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
23fbe14f 23#include <errno.h>
07630cea 24#include <fnmatch.h>
23fbe14f 25#include <inttypes.h>
07630cea 26#include <stdio.h>
23fbe14f 27#include <stdlib.h>
07630cea 28#include <string.h>
23fbe14f
TG
29#include <sys/mman.h>
30
31#include "sd-hwdb.h"
32
b5efdb8a 33#include "alloc-util.h"
3ffd4af2 34#include "fd-util.h"
23fbe14f 35#include "hashmap.h"
23fbe14f 36#include "hwdb-internal.h"
07630cea
LP
37#include "hwdb-util.h"
38#include "refcnt.h"
39#include "string-util.h"
23fbe14f
TG
40
41struct 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
23fbe14f
TG
52 OrderedHashmap *properties;
53 Iterator properties_iterator;
54 bool properties_modified;
55};
56
57struct linebuf {
58 char bytes[LINE_MAX];
59 size_t size;
60 size_t len;
61};
62
63static void linebuf_init(struct linebuf *buf) {
64 buf->size = 0;
65 buf->len = 0;
66}
67
68static 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
75static 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
52efd56a 83static bool linebuf_add_char(struct linebuf *buf, char c) {
23fbe14f
TG
84 if (buf->len + 1 >= sizeof(buf->bytes))
85 return false;
86 buf->bytes[buf->len++] = c;
87 return true;
88}
89
90static void linebuf_rem(struct linebuf *buf, size_t count) {
91 assert(buf->len >= count);
92 buf->len -= count;
93}
94
95static void linebuf_rem_char(struct linebuf *buf) {
96 linebuf_rem(buf, 1);
97}
98
8bf97636
DH
99static 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;
23fbe14f
TG
105}
106
8bf97636 107static const struct trie_value_entry_f *trie_node_value(sd_hwdb *hwdb, const struct trie_node_f *node, size_t idx) {
23fbe14f
TG
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);
8bf97636 112 base += idx * le64toh(hwdb->head->value_entry_size);
23fbe14f
TG
113 return (const struct trie_value_entry_f *)base;
114}
115
116static 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
120static const char *trie_string(sd_hwdb *hwdb, le64_t off) {
121 return hwdb->map + le64toh(off);
122}
123
124static 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
131static 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;
8bf97636 136 child = bsearch(&search, (const char *)node + le64toh(hwdb->head->node_size), node->children_count,
23fbe14f
TG
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
3a04b789
DH
143static int hwdb_add_property(sd_hwdb *hwdb, const struct trie_value_entry_f *entry) {
144 const char *key;
23fbe14f
TG
145 int r;
146
147 assert(hwdb);
3a04b789
DH
148
149 key = trie_string(hwdb, entry->key_off);
23fbe14f
TG
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
3a04b789
DH
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) {
d8646d05
ZJS
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)
3a04b789
DH
198 return 0;
199 }
200 }
201
23fbe14f
TG
202 r = ordered_hashmap_ensure_allocated(&hwdb->properties, &string_hash_ops);
203 if (r < 0)
204 return r;
205
3a04b789 206 r = ordered_hashmap_replace(hwdb->properties, key, (void *)entry);
23fbe14f
TG
207 if (r < 0)
208 return r;
209
210 hwdb->properties_modified = true;
211
212 return 0;
213}
214
215static 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++) {
8bf97636 227 const struct trie_child_entry_f *child = trie_node_child(hwdb, node, i);
23fbe14f
TG
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++) {
3a04b789 238 err = hwdb_add_property(hwdb, trie_node_value(hwdb, node, i));
23fbe14f
TG
239 if (err < 0)
240 return err;
241 }
242
243 linebuf_rem(buf, len);
244 return 0;
245}
246
247static 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++) {
0f2e01a5 264 if (IN_SET(c, '*', '?', '['))
23fbe14f
TG
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++) {
3a04b789 303 err = hwdb_add_property(hwdb, trie_node_value(hwdb, node, n));
23fbe14f
TG
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
317static const char hwdb_bin_paths[] =
52efd56a
LP
318 "/etc/systemd/hwdb/hwdb.bin\0"
319 "/etc/udev/hwdb.bin\0"
320 "/usr/lib/systemd/hwdb/hwdb.bin\0"
349cc4a5 321#if HAVE_SPLIT_USR
52efd56a 322 "/lib/systemd/hwdb/hwdb.bin\0"
23fbe14f 323#endif
52efd56a 324 UDEVLIBEXECDIR "/hwdb.bin\0";
23fbe14f
TG
325
326_public_ int sd_hwdb_new(sd_hwdb **ret) {
4afd3348 327 _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
23fbe14f
TG
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) {
331d6a20 351 log_debug("hwdb.bin does not exist, please run systemd-hwdb update");
23fbe14f
TG
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);
7e518afa 366 return -EINVAL;
23fbe14f
TG
367 }
368
369 log_debug("=== trie on-disk ===");
370 log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version));
1fa2f38f 371 log_debug("file size: %8"PRIi64" bytes", hwdb->st.st_size);
23fbe14f
TG
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) {
f0c4b1c3 391 if (hwdb && REFCNT_DEC(hwdb->n_ref) == 0) {
23fbe14f
TG
392 if (hwdb->map)
393 munmap((void *)hwdb->map, hwdb->st.st_size);
74ca738f 394 safe_fclose(hwdb->f);
23fbe14f
TG
395 ordered_hashmap_free(hwdb->properties);
396 free(hwdb);
397 }
398
399 return NULL;
400}
401
402bool 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
427static int properties_prepare(sd_hwdb *hwdb, const char *modalias) {
23fbe14f
TG
428 assert(hwdb);
429 assert(modalias);
430
23fbe14f 431 ordered_hashmap_clear(hwdb->properties);
23fbe14f
TG
432 hwdb->properties_modified = true;
433
1cd592b9 434 return trie_search_f(hwdb, modalias);
23fbe14f
TG
435}
436
437_public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **_value) {
3a04b789 438 const struct trie_value_entry_f *entry;
23fbe14f
TG
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
3a04b789
DH
450 entry = ordered_hashmap_get(hwdb->properties, key);
451 if (!entry)
23fbe14f
TG
452 return -ENOENT;
453
3a04b789 454 *_value = trie_string(hwdb, entry->value_off);
23fbe14f
TG
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) {
3a04b789 477 const struct trie_value_entry_f *entry;
8927b1da 478 const void *k;
23fbe14f
TG
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
3a04b789 487 ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, (void **)&entry, &k);
23fbe14f
TG
488 if (!k)
489 return 0;
490
491 *key = k;
3a04b789 492 *value = trie_string(hwdb, entry->value_off);
23fbe14f
TG
493
494 return 1;
495}