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