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