1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 Copyright © 2008 Alan Jenkins <alan.christopher.jenkins@googlemail.com>
16 #include "alloc-util.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"
31 struct trie_header_f
*head
;
35 OrderedHashmap
*properties
;
36 Iterator properties_iterator
;
37 bool properties_modified
;
46 static void linebuf_init(struct linebuf
*buf
) {
51 static const char *linebuf_get(struct linebuf
*buf
) {
52 if (buf
->len
+ 1 >= sizeof(buf
->bytes
))
54 buf
->bytes
[buf
->len
] = '\0';
58 static bool linebuf_add(struct linebuf
*buf
, const char *s
, size_t len
) {
59 if (buf
->len
+ len
>= sizeof(buf
->bytes
))
61 memcpy(buf
->bytes
+ buf
->len
, s
, len
);
66 static bool linebuf_add_char(struct linebuf
*buf
, char c
) {
67 if (buf
->len
+ 1 >= sizeof(buf
->bytes
))
69 buf
->bytes
[buf
->len
++] = c
;
73 static void linebuf_rem(struct linebuf
*buf
, size_t count
) {
74 assert(buf
->len
>= count
);
78 static void linebuf_rem_char(struct linebuf
*buf
) {
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
;
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
;
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
;
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
;
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
));
103 static const char *trie_string(sd_hwdb
*hwdb
, le64_t off
) {
104 return hwdb
->map
+ le64toh(off
);
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
;
111 return n1
->c
- n2
->c
;
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
;
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
);
122 return trie_node_from_off(hwdb
, child
->child_off
);
126 static int hwdb_add_property(sd_hwdb
*hwdb
, const struct trie_value_entry_f
*entry
) {
132 key
= trie_string(hwdb
, entry
->key_off
);
135 * Silently ignore all properties which do not start with a
136 * space; future extensions might use additional prefixes.
143 if (le64toh(hwdb
->head
->value_entry_size
) >= sizeof(struct trie_value_entry2_f
)) {
144 const struct trie_value_entry2_f
*old
, *entry2
;
146 entry2
= (const struct trie_value_entry2_f
*)entry
;
147 old
= ordered_hashmap_get(hwdb
->properties
, key
);
149 /* On duplicates, we order by filename priority and line-number.
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
157 * For v3 data: we compare the priority (of the source file)
158 * and the line number.
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.
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
);
177 lower
= entry2
->file_priority
< old
->file_priority
||
178 (entry2
->file_priority
== old
->file_priority
&& entry2
->line_number
< old
->line_number
);
184 r
= ordered_hashmap_ensure_allocated(&hwdb
->properties
, &string_hash_ops
);
188 r
= ordered_hashmap_replace(hwdb
->properties
, key
, (void *)entry
);
192 hwdb
->properties_modified
= true;
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
) {
204 prefix
= trie_string(hwdb
, node
->prefix_off
);
205 len
= strlen(prefix
+ p
);
206 linebuf_add(buf
, prefix
+ p
, len
);
208 for (i
= 0; i
< node
->children_count
; i
++) {
209 const struct trie_child_entry_f
*child
= trie_node_child(hwdb
, node
, i
);
211 linebuf_add_char(buf
, child
->c
);
212 err
= trie_fnmatch_f(hwdb
, trie_node_from_off(hwdb
, child
->child_off
), 0, buf
, search
);
215 linebuf_rem_char(buf
);
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
));
225 linebuf_rem(buf
, len
);
229 static int trie_search_f(sd_hwdb
*hwdb
, const char *search
) {
231 const struct trie_node_f
*node
;
237 node
= trie_node_from_off(hwdb
, hwdb
->head
->nodes_root_off
);
239 const struct trie_node_f
*child
;
242 if (node
->prefix_off
) {
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
])
254 child
= node_lookup_f(hwdb
, node
, '*');
256 linebuf_add_char(&buf
, '*');
257 err
= trie_fnmatch_f(hwdb
, child
, 0, &buf
, search
+ i
);
260 linebuf_rem_char(&buf
);
263 child
= node_lookup_f(hwdb
, node
, '?');
265 linebuf_add_char(&buf
, '?');
266 err
= trie_fnmatch_f(hwdb
, child
, 0, &buf
, search
+ i
);
269 linebuf_rem_char(&buf
);
272 child
= node_lookup_f(hwdb
, node
, '[');
274 linebuf_add_char(&buf
, '[');
275 err
= trie_fnmatch_f(hwdb
, child
, 0, &buf
, search
+ i
);
278 linebuf_rem_char(&buf
);
281 if (search
[i
] == '\0') {
284 for (n
= 0; n
< le64toh(node
->values_count
); n
++) {
285 err
= hwdb_add_property(hwdb
, trie_node_value(hwdb
, node
, n
));
292 child
= node_lookup_f(hwdb
, node
, search
[i
]);
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"
304 "/lib/systemd/hwdb/hwdb.bin\0"
306 UDEVLIBEXECDIR
"/hwdb.bin\0";
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
;
313 assert_return(ret
, -EINVAL
);
315 hwdb
= new0(sd_hwdb
, 1);
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");
328 return log_debug_errno(errno
, "Failed to open %s: %m", hwdb_bin_path
);
332 return log_debug_errno(SYNTHETIC_ERRNO(ENOENT
),
333 "hwdb.bin does not exist, please run 'systemd-hwdb update'");
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
);
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
);
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",
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
));
358 *ret
= TAKE_PTR(hwdb
);
363 static sd_hwdb
*hwdb_free(sd_hwdb
*hwdb
) {
367 munmap((void *)hwdb
->map
, hwdb
->st
.st_size
);
368 safe_fclose(hwdb
->f
);
369 ordered_hashmap_free(hwdb
->properties
);
373 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_hwdb
, sd_hwdb
, hwdb_free
)
375 bool hwdb_validate(sd_hwdb
*hwdb
) {
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) {
394 if (timespec_load(&hwdb
->st
.st_mtim
) != timespec_load(&st
.st_mtim
))
399 static int properties_prepare(sd_hwdb
*hwdb
, const char *modalias
) {
403 ordered_hashmap_clear(hwdb
->properties
);
404 hwdb
->properties_modified
= true;
406 return trie_search_f(hwdb
, modalias
);
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
;
413 assert_return(hwdb
, -EINVAL
);
414 assert_return(hwdb
->f
, -EINVAL
);
415 assert_return(modalias
, -EINVAL
);
416 assert_return(_value
, -EINVAL
);
418 r
= properties_prepare(hwdb
, modalias
);
422 entry
= ordered_hashmap_get(hwdb
->properties
, key
);
426 *_value
= trie_string(hwdb
, entry
->value_off
);
431 _public_
int sd_hwdb_seek(sd_hwdb
*hwdb
, const char *modalias
) {
434 assert_return(hwdb
, -EINVAL
);
435 assert_return(hwdb
->f
, -EINVAL
);
436 assert_return(modalias
, -EINVAL
);
438 r
= properties_prepare(hwdb
, modalias
);
442 hwdb
->properties_modified
= false;
443 hwdb
->properties_iterator
= ITERATOR_FIRST
;
448 _public_
int sd_hwdb_enumerate(sd_hwdb
*hwdb
, const char **key
, const char **value
) {
449 const struct trie_value_entry_f
*entry
;
452 assert_return(hwdb
, -EINVAL
);
453 assert_return(key
, -EINVAL
);
454 assert_return(value
, -EINVAL
);
456 if (hwdb
->properties_modified
)
459 if (!ordered_hashmap_iterate(hwdb
->properties
, &hwdb
->properties_iterator
, (void **)&entry
, &k
))
463 *value
= trie_string(hwdb
, entry
->value_off
);