]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/hwdb-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "alloc-util.h"
8 #include "conf-files.h"
13 #include "hwdb-internal.h"
14 #include "hwdb-util.h"
15 #include "label-util.h"
16 #include "mkdir-label.h"
17 #include "nulstr-util.h"
18 #include "path-util.h"
19 #include "sort-util.h"
21 #include "string-util.h"
23 #include "tmpfile-util.h"
25 static const char* const conf_file_dirs
[] = {
27 UDEVLIBEXECDIR
"/hwdb.d",
32 * Generic udev properties, key-value database based on modalias strings.
33 * Uses a Patricia/radix trie to index all matches for efficient lookup.
36 /* in-memory trie objects */
38 struct trie_node
*root
;
39 struct strbuf
*strings
;
42 size_t children_count
;
47 /* prefix, common part for all children of this node */
50 /* sorted array of pointers to children nodes */
51 struct trie_child_entry
*children
;
52 uint8_t children_count
;
54 /* sorted array of key-value pairs */
55 struct trie_value_entry
*values
;
59 /* children array item with char (0-255) index */
60 struct trie_child_entry
{
62 struct trie_node
*child
;
65 /* value array item with key-value pairs */
66 struct trie_value_entry
{
71 uint16_t file_priority
;
74 static int trie_children_cmp(const struct trie_child_entry
*a
, const struct trie_child_entry
*b
) {
75 return CMP(a
->c
, b
->c
);
78 static int node_add_child(struct trie
*trie
, struct trie_node
*node
, struct trie_node
*node_child
, uint8_t c
) {
79 struct trie_child_entry
*child
;
81 /* extend array, add new entry, sort for bisection */
82 child
= reallocarray(node
->children
, node
->children_count
+ 1, sizeof(struct trie_child_entry
));
86 node
->children
= child
;
87 trie
->children_count
++;
88 node
->children
[node
->children_count
].c
= c
;
89 node
->children
[node
->children_count
].child
= node_child
;
90 node
->children_count
++;
91 typesafe_qsort(node
->children
, node
->children_count
, trie_children_cmp
);
97 static struct trie_node
*node_lookup(const struct trie_node
*node
, uint8_t c
) {
98 struct trie_child_entry
*child
;
99 struct trie_child_entry search
;
102 child
= typesafe_bsearch(&search
, node
->children
, node
->children_count
, trie_children_cmp
);
108 static void trie_node_cleanup(struct trie_node
*node
) {
112 for (size_t i
= 0; i
< node
->children_count
; i
++)
113 trie_node_cleanup(node
->children
[i
].child
);
114 free(node
->children
);
119 static struct trie
* trie_free(struct trie
*trie
) {
123 trie_node_cleanup(trie
->root
);
124 strbuf_free(trie
->strings
);
128 DEFINE_TRIVIAL_CLEANUP_FUNC(struct trie
*, trie_free
);
130 static int trie_values_cmp(const struct trie_value_entry
*a
, const struct trie_value_entry
*b
, struct trie
*trie
) {
131 return strcmp(trie
->strings
->buf
+ a
->key_off
,
132 trie
->strings
->buf
+ b
->key_off
);
135 static int trie_node_add_value(struct trie
*trie
, struct trie_node
*node
,
136 const char *key
, const char *value
,
137 const char *filename
, uint16_t file_priority
, uint32_t line_number
, bool compat
) {
138 ssize_t k
, v
, fn
= 0;
139 struct trie_value_entry
*val
;
141 k
= strbuf_add_string(trie
->strings
, key
, strlen(key
));
144 v
= strbuf_add_string(trie
->strings
, value
, strlen(value
));
149 fn
= strbuf_add_string(trie
->strings
, filename
, strlen(filename
));
154 if (node
->values_count
) {
155 struct trie_value_entry search
= {
160 val
= typesafe_bsearch_r(&search
, node
->values
, node
->values_count
, trie_values_cmp
, trie
);
162 /* At this point we have 2 identical properties on the same match-string.
163 * Since we process files in order, we just replace the previous value. */
165 val
->filename_off
= fn
;
166 val
->file_priority
= file_priority
;
167 val
->line_number
= line_number
;
172 /* extend array, add new entry, sort for bisection */
173 val
= reallocarray(node
->values
, node
->values_count
+ 1, sizeof(struct trie_value_entry
));
176 trie
->values_count
++;
178 node
->values
[node
->values_count
] = (struct trie_value_entry
) {
182 .file_priority
= file_priority
,
183 .line_number
= line_number
,
185 node
->values_count
++;
186 typesafe_qsort_r(node
->values
, node
->values_count
, trie_values_cmp
, trie
);
190 static int trie_insert(struct trie
*trie
, struct trie_node
*node
, const char *search
,
191 const char *key
, const char *value
,
192 const char *filename
, uint16_t file_priority
, uint32_t line_number
, bool compat
) {
195 for (size_t i
= 0;; i
++) {
198 struct trie_node
*child
;
200 for (p
= 0; (c
= trie
->strings
->buf
[node
->prefix_off
+ p
]); p
++) {
201 _cleanup_free_
struct trie_node
*new_child
= NULL
;
202 _cleanup_free_
char *s
= NULL
;
205 if (c
== search
[i
+ p
])
209 new_child
= new(struct trie_node
, 1);
213 /* move values from parent to child */
214 *new_child
= (struct trie_node
) {
215 .prefix_off
= node
->prefix_off
+ p
+1,
216 .children
= node
->children
,
217 .children_count
= node
->children_count
,
218 .values
= node
->values
,
219 .values_count
= node
->values_count
,
222 /* update parent; use strdup() because the source gets realloc()d */
223 s
= strndup(trie
->strings
->buf
+ node
->prefix_off
, p
);
227 off
= strbuf_add_string(trie
->strings
, s
, p
);
231 *node
= (struct trie_node
) {
234 r
= node_add_child(trie
, node
, new_child
, c
);
238 new_child
= NULL
; /* avoid cleanup */
245 return trie_node_add_value(trie
, node
, key
, value
, filename
, file_priority
, line_number
, compat
);
247 child
= node_lookup(node
, c
);
249 _cleanup_free_
struct trie_node
*new_child
= NULL
;
253 new_child
= new(struct trie_node
, 1);
257 off
= strbuf_add_string(trie
->strings
, search
+ i
+1, strlen(search
+ i
+1));
261 *new_child
= (struct trie_node
) {
265 r
= node_add_child(trie
, node
, new_child
, c
);
269 child
= TAKE_PTR(new_child
);
270 return trie_node_add_value(trie
, child
, key
, value
, filename
, file_priority
, line_number
, compat
);
279 uint64_t strings_off
;
281 uint64_t nodes_count
;
282 uint64_t children_count
;
283 uint64_t values_count
;
286 /* calculate the storage space for the nodes, children arrays, value arrays */
287 static void trie_store_nodes_size(struct trie_f
*trie
, struct trie_node
*node
, bool compat
) {
288 for (uint64_t i
= 0; i
< node
->children_count
; i
++)
289 trie_store_nodes_size(trie
, node
->children
[i
].child
, compat
);
291 trie
->strings_off
+= sizeof(struct trie_node_f
);
292 for (uint64_t i
= 0; i
< node
->children_count
; i
++)
293 trie
->strings_off
+= sizeof(struct trie_child_entry_f
);
294 for (uint64_t i
= 0; i
< node
->values_count
; i
++)
295 trie
->strings_off
+= compat
? sizeof(struct trie_value_entry_f
) : sizeof(struct trie_value_entry2_f
);
298 static int64_t trie_store_nodes(struct trie_f
*trie
, FILE *f
, struct trie_node
*node
, bool compat
) {
299 _cleanup_free_
struct trie_child_entry_f
*children
= NULL
;
306 if (node
->children_count
) {
307 children
= new(struct trie_child_entry_f
, node
->children_count
);
312 /* post-order recursion */
313 for (uint64_t i
= 0; i
< node
->children_count
; i
++) {
316 child_off
= trie_store_nodes(trie
, f
, node
->children
[i
].child
, compat
);
320 children
[i
] = (struct trie_child_entry_f
) {
321 .c
= node
->children
[i
].c
,
322 .child_off
= htole64(child_off
),
326 struct trie_node_f n
= {
327 .prefix_off
= htole64(trie
->strings_off
+ node
->prefix_off
),
328 .children_count
= node
->children_count
,
329 .values_count
= htole64(node
->values_count
),
333 node_off
= ftello(f
);
334 fwrite(&n
, sizeof(struct trie_node_f
), 1, f
);
337 /* append children array */
338 if (node
->children_count
) {
339 fwrite(children
, sizeof(struct trie_child_entry_f
), node
->children_count
, f
);
340 trie
->children_count
+= node
->children_count
;
343 /* append values array */
344 for (uint64_t i
= 0; i
< node
->values_count
; i
++) {
345 struct trie_value_entry2_f v
= {
346 .key_off
= htole64(trie
->strings_off
+ node
->values
[i
].key_off
),
347 .value_off
= htole64(trie
->strings_off
+ node
->values
[i
].value_off
),
348 .filename_off
= htole64(trie
->strings_off
+ node
->values
[i
].filename_off
),
349 .line_number
= htole32(node
->values
[i
].line_number
),
350 .file_priority
= htole16(node
->values
[i
].file_priority
),
353 fwrite(&v
, compat
? sizeof(struct trie_value_entry_f
) : sizeof(struct trie_value_entry2_f
), 1, f
);
355 trie
->values_count
+= node
->values_count
;
360 static int trie_store(struct trie
*trie
, const char *filename
, bool compat
) {
363 .strings_off
= sizeof(struct trie_header_f
),
365 _cleanup_(unlink_and_freep
) char *filename_tmp
= NULL
;
366 _cleanup_fclose_
FILE *f
= NULL
;
367 int64_t pos
, root_off
, size
;
373 /* calculate size of header, nodes, children entries, value entries */
374 trie_store_nodes_size(&t
, trie
->root
, compat
);
376 r
= fopen_tmpfile_linkable(filename
, O_WRONLY
|O_CLOEXEC
, &filename_tmp
, &f
);
380 if (fchmod(fileno(f
), 0444) < 0)
383 struct trie_header_f h
= {
384 .signature
= HWDB_SIG
,
385 .tool_version
= htole64(PROJECT_VERSION
),
386 .header_size
= htole64(sizeof(struct trie_header_f
)),
387 .node_size
= htole64(sizeof(struct trie_node_f
)),
388 .child_entry_size
= htole64(sizeof(struct trie_child_entry_f
)),
389 .value_entry_size
= htole64(compat
? sizeof(struct trie_value_entry_f
) : sizeof(struct trie_value_entry2_f
)),
393 if (fseeko(f
, sizeof(struct trie_header_f
), SEEK_SET
) < 0)
396 root_off
= trie_store_nodes(&t
, f
, trie
->root
, compat
);
397 h
.nodes_root_off
= htole64(root_off
);
399 h
.nodes_len
= htole64(pos
- sizeof(struct trie_header_f
));
401 /* write string buffer */
402 fwrite(trie
->strings
->buf
, trie
->strings
->len
, 1, f
);
403 h
.strings_len
= htole64(trie
->strings
->len
);
407 h
.file_size
= htole64(size
);
408 if (fseeko(f
, 0, SEEK_SET
) < 0)
410 fwrite(&h
, sizeof(struct trie_header_f
), 1, f
);
412 r
= flink_tmpfile(f
, filename_tmp
, filename
, LINK_TMPFILE_REPLACE
|LINK_TMPFILE_SYNC
);
416 /* write succeeded */
418 log_debug("=== trie on-disk ===");
419 log_debug("size: %8"PRIi64
" bytes", size
);
420 log_debug("header: %8zu bytes", sizeof(struct trie_header_f
));
421 log_debug("nodes: %8"PRIu64
" bytes (%8"PRIu64
")",
422 t
.nodes_count
* sizeof(struct trie_node_f
), t
.nodes_count
);
423 log_debug("child pointers: %8"PRIu64
" bytes (%8"PRIu64
")",
424 t
.children_count
* sizeof(struct trie_child_entry_f
), t
.children_count
);
425 log_debug("value pointers: %8"PRIu64
" bytes (%8"PRIu64
")",
426 t
.values_count
* (compat
? sizeof(struct trie_value_entry_f
) : sizeof(struct trie_value_entry2_f
)), t
.values_count
);
427 log_debug("string store: %8zu bytes", trie
->strings
->len
);
428 log_debug("strings start: %8"PRIu64
, t
.strings_off
);
432 static int insert_data(struct trie
*trie
, char **match_list
, char *line
, const char *filename
,
433 uint16_t file_priority
, uint32_t line_number
, bool compat
) {
436 assert(line
[0] == ' ');
438 value
= strchr(line
, '=');
440 return log_syntax(NULL
, LOG_WARNING
, filename
, line_number
, SYNTHETIC_ERRNO(EINVAL
),
441 "Key-value pair expected but got \"%s\", ignoring.", line
);
446 /* Replace multiple leading spaces by a single space */
447 while (isblank(line
[0]) && isblank(line
[1]))
450 if (isempty(line
+ 1))
451 return log_syntax(NULL
, LOG_WARNING
, filename
, line_number
, SYNTHETIC_ERRNO(EINVAL
),
452 "Empty key in \"%s=%s\", ignoring.",
455 STRV_FOREACH(entry
, match_list
)
456 trie_insert(trie
, trie
->root
, *entry
, line
, value
, filename
, file_priority
, line_number
, compat
);
461 static int import_file(struct trie
*trie
, const char *filename
, uint16_t file_priority
, bool compat
) {
467 _cleanup_fclose_
FILE *f
= NULL
;
468 _cleanup_strv_free_
char **match_list
= NULL
;
469 uint32_t line_number
= 0;
472 f
= fopen(filename
, "re");
477 _cleanup_free_
char *line
= NULL
;
481 r
= read_line_full(f
, LONG_LINE_MAX
, READ_LINE_NOT_A_TTY
, &line
);
493 /* strip trailing comment */
494 pos
= strchr(line
, '#');
498 /* strip trailing whitespace */
500 while (len
> 0 && isspace(line
[len
-1]))
509 if (line
[0] == ' ') {
510 r
= log_syntax(NULL
, LOG_WARNING
, filename
, line_number
, SYNTHETIC_ERRNO(EINVAL
),
511 "Match expected but got indented property \"%s\", ignoring line.", line
);
515 /* start of record, first match */
518 err
= strv_extend(&match_list
, line
);
526 r
= log_syntax(NULL
, LOG_WARNING
, filename
, line_number
, SYNTHETIC_ERRNO(EINVAL
),
527 "Property expected, ignoring record with no properties.");
529 match_list
= strv_free(match_list
);
533 if (line
[0] != ' ') {
535 err
= strv_extend(&match_list
, line
);
544 err
= insert_data(trie
, match_list
, line
, filename
, file_priority
, line_number
, compat
);
553 match_list
= strv_free(match_list
);
557 if (line
[0] != ' ') {
558 r
= log_syntax(NULL
, LOG_WARNING
, filename
, line_number
, SYNTHETIC_ERRNO(EINVAL
),
559 "Property or empty line expected, got \"%s\", ignoring record.", line
);
561 match_list
= strv_free(match_list
);
565 err
= insert_data(trie
, match_list
, line
, filename
, file_priority
, line_number
, compat
);
572 if (state
== HW_MATCH
)
573 log_syntax(NULL
, LOG_WARNING
, filename
, line_number
, 0,
574 "Property expected, ignoring record with no properties.");
579 int hwdb_update(const char *root
, const char *hwdb_bin_dir
, bool strict
, bool compat
) {
580 _cleanup_free_
char *hwdb_bin
= NULL
;
581 _cleanup_(trie_freep
) struct trie
*trie
= NULL
;
582 _cleanup_strv_free_
char **files
= NULL
;
583 uint16_t file_priority
= 1;
586 /* The argument 'compat' controls the format version of database. If false, then hwdb.bin will be
587 * created with additional information such that priority, line number, and filename of database
588 * source. If true, then hwdb.bin will be created without the information. systemd-hwdb command
589 * should set the argument false, and 'udevadm hwdb' command should set it true. */
591 hwdb_bin
= path_join(root
, hwdb_bin_dir
?: "/etc/udev", "hwdb.bin");
595 trie
= new0(struct trie
, 1);
600 trie
->strings
= strbuf_new();
605 trie
->root
= new0(struct trie_node
, 1);
611 err
= conf_files_list_strv(&files
, ".hwdb", root
, 0, conf_file_dirs
);
613 return log_error_errno(err
, "Failed to enumerate hwdb files: %m");
615 if (strv_isempty(files
)) {
616 if (unlink(hwdb_bin
) < 0) {
618 return log_error_errno(errno
, "Failed to remove compiled hwdb database %s: %m", hwdb_bin
);
620 log_info("No hwdb files found, skipping.");
622 log_info("No hwdb files found, compiled hwdb database %s removed.", hwdb_bin
);
627 STRV_FOREACH(f
, files
) {
628 log_debug("Reading file \"%s\"", *f
);
629 err
= import_file(trie
, *f
, file_priority
++, compat
);
630 if (err
< 0 && strict
)
634 strbuf_complete(trie
->strings
);
636 log_debug("=== trie in-memory ===");
637 log_debug("nodes: %8zu bytes (%8zu)",
638 trie
->nodes_count
* sizeof(struct trie_node
), trie
->nodes_count
);
639 log_debug("children arrays: %8zu bytes (%8zu)",
640 trie
->children_count
* sizeof(struct trie_child_entry
), trie
->children_count
);
641 log_debug("values arrays: %8zu bytes (%8zu)",
642 trie
->values_count
* sizeof(struct trie_value_entry
), trie
->values_count
);
643 log_debug("strings: %8zu bytes",
645 log_debug("strings incoming: %8zu bytes (%8zu)",
646 trie
->strings
->in_len
, trie
->strings
->in_count
);
647 log_debug("strings dedup'ed: %8zu bytes (%8zu)",
648 trie
->strings
->dedup_len
, trie
->strings
->dedup_count
);
650 (void) mkdir_parents_label(hwdb_bin
, 0755);
651 err
= trie_store(trie
, hwdb_bin
, compat
);
653 return log_error_errno(err
, "Failed to write database %s: %m", hwdb_bin
);
655 err
= label_fix(hwdb_bin
, 0);
662 int hwdb_query(const char *modalias
, const char *root
) {
663 _cleanup_(sd_hwdb_unrefp
) sd_hwdb
*hwdb
= NULL
;
664 const char *key
, *value
;
670 NULSTR_FOREACH(p
, hwdb_bin_paths
) {
671 _cleanup_free_
char *hwdb_bin
= NULL
;
673 hwdb_bin
= path_join(root
, p
);
677 r
= sd_hwdb_new_from_path(hwdb_bin
, &hwdb
);
682 r
= sd_hwdb_new(&hwdb
);
686 SD_HWDB_FOREACH_PROPERTY(hwdb
, modalias
, key
, value
)
687 printf("%s=%s\n", key
, value
);
692 bool hwdb_should_reload(sd_hwdb
*hwdb
) {
701 /* if hwdb.bin doesn't exist anywhere, we need to update */
702 NULSTR_FOREACH(p
, hwdb_bin_paths
)
703 if (stat(p
, &st
) >= 0) {
710 if (timespec_load(&hwdb
->st
.st_mtim
) != timespec_load(&st
.st_mtim
))
715 int hwdb_bypass(void) {
718 r
= getenv_bool("SYSTEMD_HWDB_UPDATE_BYPASS");
719 if (r
< 0 && r
!= -ENXIO
)
720 log_debug_errno(r
, "Failed to parse $SYSTEMD_HWDB_UPDATE_BYPASS, assuming no.");
724 log_debug("$SYSTEMD_HWDB_UPDATE_BYPASS is enabled, skipping execution.");