Coverage Report

Created: 2025-11-16 06:36

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