1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 Copyright © 2012 Kay Sievers <kay@vrfy.org>
11 #include "alloc-util.h"
12 #include "conf-files.h"
15 #include "hwdb-internal.h"
16 #include "hwdb-util.h"
20 #include "string-util.h"
22 #include "udevadm-util.h"
26 * Generic udev properties, key/value database based on modalias strings.
27 * Uses a Patricia/radix trie to index all matches for efficient lookup.
30 static const char * const conf_file_dirs
[] = {
32 UDEVLIBEXECDIR
"/hwdb.d",
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 static int trie_children_cmp(const void *v1
, const void *v2
) {
72 const struct trie_child_entry
*n1
= v1
;
73 const struct trie_child_entry
*n2
= v2
;
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 qsort(node
->children
, node
->children_count
, sizeof(struct trie_child_entry
), 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
= bsearch_safe(&search
,
103 node
->children
, node
->children_count
, sizeof(struct trie_child_entry
),
110 static void trie_node_cleanup(struct trie_node
*node
) {
113 for (i
= 0; i
< node
->children_count
; i
++)
114 trie_node_cleanup(node
->children
[i
].child
);
115 free(node
->children
);
120 static int trie_values_cmp(const void *v1
, const void *v2
, void *arg
) {
121 const struct trie_value_entry
*val1
= v1
;
122 const struct trie_value_entry
*val2
= v2
;
123 struct trie
*trie
= arg
;
125 return strcmp(trie
->strings
->buf
+ val1
->key_off
,
126 trie
->strings
->buf
+ val2
->key_off
);
129 static int trie_node_add_value(struct trie
*trie
, struct trie_node
*node
,
130 const char *key
, const char *value
) {
132 struct trie_value_entry
*val
;
134 k
= strbuf_add_string(trie
->strings
, key
, strlen(key
));
137 v
= strbuf_add_string(trie
->strings
, value
, strlen(value
));
141 if (node
->values_count
) {
142 struct trie_value_entry search
= {
147 val
= xbsearch_r(&search
, node
->values
, node
->values_count
, sizeof(struct trie_value_entry
), trie_values_cmp
, trie
);
149 /* replace existing earlier key with new value */
155 /* extend array, add new entry, sort for bisection */
156 val
= reallocarray(node
->values
, node
->values_count
+ 1, sizeof(struct trie_value_entry
));
159 trie
->values_count
++;
161 node
->values
[node
->values_count
].key_off
= k
;
162 node
->values
[node
->values_count
].value_off
= v
;
163 node
->values_count
++;
164 qsort_r(node
->values
, node
->values_count
, sizeof(struct trie_value_entry
), trie_values_cmp
, trie
);
168 static int trie_insert(struct trie
*trie
, struct trie_node
*node
, const char *search
,
169 const char *key
, const char *value
) {
176 struct trie_node
*child
;
178 for (p
= 0; (c
= trie
->strings
->buf
[node
->prefix_off
+ p
]); p
++) {
179 _cleanup_free_
char *s
= NULL
;
181 _cleanup_free_
struct trie_node
*new_child
= NULL
;
183 if (c
== search
[i
+ p
])
187 new_child
= new0(struct trie_node
, 1);
191 /* move values from parent to child */
192 new_child
->prefix_off
= node
->prefix_off
+ p
+1;
193 new_child
->children
= node
->children
;
194 new_child
->children_count
= node
->children_count
;
195 new_child
->values
= node
->values
;
196 new_child
->values_count
= node
->values_count
;
198 /* update parent; use strdup() because the source gets realloc()d */
199 s
= strndup(trie
->strings
->buf
+ node
->prefix_off
, p
);
203 off
= strbuf_add_string(trie
->strings
, s
, p
);
207 node
->prefix_off
= off
;
208 node
->children
= NULL
;
209 node
->children_count
= 0;
211 node
->values_count
= 0;
212 err
= node_add_child(trie
, node
, new_child
, c
);
216 new_child
= NULL
; /* avoid cleanup */
223 return trie_node_add_value(trie
, node
, key
, value
);
225 child
= node_lookup(node
, c
);
230 child
= new0(struct trie_node
, 1);
234 off
= strbuf_add_string(trie
->strings
, search
+ i
+1, strlen(search
+ i
+1));
240 child
->prefix_off
= off
;
241 err
= node_add_child(trie
, node
, child
, c
);
247 return trie_node_add_value(trie
, child
, key
, value
);
258 uint64_t strings_off
;
260 uint64_t nodes_count
;
261 uint64_t children_count
;
262 uint64_t values_count
;
265 /* calculate the storage space for the nodes, children arrays, value arrays */
266 static void trie_store_nodes_size(struct trie_f
*trie
, struct trie_node
*node
) {
269 for (i
= 0; i
< node
->children_count
; i
++)
270 trie_store_nodes_size(trie
, node
->children
[i
].child
);
272 trie
->strings_off
+= sizeof(struct trie_node_f
);
273 for (i
= 0; i
< node
->children_count
; i
++)
274 trie
->strings_off
+= sizeof(struct trie_child_entry_f
);
275 for (i
= 0; i
< node
->values_count
; i
++)
276 trie
->strings_off
+= sizeof(struct trie_value_entry_f
);
279 static int64_t trie_store_nodes(struct trie_f
*trie
, struct trie_node
*node
) {
281 struct trie_node_f n
= {
282 .prefix_off
= htole64(trie
->strings_off
+ node
->prefix_off
),
283 .children_count
= node
->children_count
,
284 .values_count
= htole64(node
->values_count
),
286 struct trie_child_entry_f
*children
= NULL
;
289 if (node
->children_count
) {
290 children
= new0(struct trie_child_entry_f
, node
->children_count
);
295 /* post-order recursion */
296 for (i
= 0; i
< node
->children_count
; i
++) {
299 child_off
= trie_store_nodes(trie
, node
->children
[i
].child
);
304 children
[i
].c
= node
->children
[i
].c
;
305 children
[i
].child_off
= htole64(child_off
);
309 node_off
= ftello(trie
->f
);
310 fwrite(&n
, sizeof(struct trie_node_f
), 1, trie
->f
);
313 /* append children array */
314 if (node
->children_count
) {
315 fwrite(children
, sizeof(struct trie_child_entry_f
), node
->children_count
, trie
->f
);
316 trie
->children_count
+= node
->children_count
;
320 /* append values array */
321 for (i
= 0; i
< node
->values_count
; i
++) {
322 struct trie_value_entry_f v
= {
323 .key_off
= htole64(trie
->strings_off
+ node
->values
[i
].key_off
),
324 .value_off
= htole64(trie
->strings_off
+ node
->values
[i
].value_off
),
327 fwrite(&v
, sizeof(struct trie_value_entry_f
), 1, trie
->f
);
328 trie
->values_count
++;
334 static int trie_store(struct trie
*trie
, const char *filename
) {
338 _cleanup_free_
char *filename_tmp
= NULL
;
342 struct trie_header_f h
= {
343 .signature
= HWDB_SIG
,
344 .tool_version
= htole64(atoi(PACKAGE_VERSION
)),
345 .header_size
= htole64(sizeof(struct trie_header_f
)),
346 .node_size
= htole64(sizeof(struct trie_node_f
)),
347 .child_entry_size
= htole64(sizeof(struct trie_child_entry_f
)),
348 .value_entry_size
= htole64(sizeof(struct trie_value_entry_f
)),
352 /* calculate size of header, nodes, children entries, value entries */
353 t
.strings_off
= sizeof(struct trie_header_f
);
354 trie_store_nodes_size(&t
, trie
->root
);
356 err
= fopen_temporary(filename
, &t
.f
, &filename_tmp
);
359 fchmod(fileno(t
.f
), 0444);
362 if (fseeko(t
.f
, sizeof(struct trie_header_f
), SEEK_SET
) < 0)
364 root_off
= trie_store_nodes(&t
, trie
->root
);
365 h
.nodes_root_off
= htole64(root_off
);
367 h
.nodes_len
= htole64(pos
- sizeof(struct trie_header_f
));
369 /* write string buffer */
370 fwrite(trie
->strings
->buf
, trie
->strings
->len
, 1, t
.f
);
371 h
.strings_len
= htole64(trie
->strings
->len
);
375 h
.file_size
= htole64(size
);
376 if (fseeko(t
.f
, 0, SEEK_SET
< 0))
378 fwrite(&h
, sizeof(struct trie_header_f
), 1, t
.f
);
384 if (fsync(fileno(t
.f
)) < 0)
386 if (rename(filename_tmp
, filename
) < 0)
389 /* write succeeded */
392 log_debug("=== trie on-disk ===");
393 log_debug("size: %8"PRIi64
" bytes", size
);
394 log_debug("header: %8zu bytes", sizeof(struct trie_header_f
));
395 log_debug("nodes: %8"PRIu64
" bytes (%8"PRIu64
")",
396 t
.nodes_count
* sizeof(struct trie_node_f
), t
.nodes_count
);
397 log_debug("child pointers: %8"PRIu64
" bytes (%8"PRIu64
")",
398 t
.children_count
* sizeof(struct trie_child_entry_f
), t
.children_count
);
399 log_debug("value pointers: %8"PRIu64
" bytes (%8"PRIu64
")",
400 t
.values_count
* sizeof(struct trie_value_entry_f
), t
.values_count
);
401 log_debug("string store: %8zu bytes", trie
->strings
->len
);
402 log_debug("strings start: %8"PRIu64
, t
.strings_off
);
409 unlink(filename_tmp
);
413 static int insert_data(struct trie
*trie
, struct udev_list
*match_list
,
414 char *line
, const char *filename
) {
416 struct udev_list_entry
*entry
;
418 value
= strchr(line
, '=');
420 log_error("Error, key/value pair expected but got '%s' in '%s':", line
, filename
);
427 /* libudev requires properties to start with a space */
428 while (isblank(line
[0]) && isblank(line
[1]))
431 if (line
[0] == '\0' || value
[0] == '\0') {
432 log_error("Error, empty key or value '%s' in '%s':", line
, filename
);
436 udev_list_entry_foreach(entry
, udev_list_get_entry(match_list
))
437 trie_insert(trie
, trie
->root
, udev_list_entry_get_name(entry
), line
, value
);
442 static int import_file(struct udev
*udev
, struct trie
*trie
, const char *filename
) {
450 struct udev_list match_list
;
453 udev_list_init(udev
, &match_list
, false);
455 f
= fopen(filename
, "re");
459 while (fgets(line
, sizeof(line
), f
)) {
467 /* strip trailing comment */
468 pos
= strchr(line
, '#');
472 /* strip trailing whitespace */
474 while (len
> 0 && isspace(line
[len
-1]))
483 if (line
[0] == ' ') {
484 log_error("Error, MATCH expected but got '%s' in '%s':", line
, filename
);
489 /* start of record, first match */
491 udev_list_entry_add(&match_list
, line
, NULL
);
496 log_error("Error, DATA expected but got empty line in '%s':", filename
);
499 udev_list_cleanup(&match_list
);
504 if (line
[0] != ' ') {
505 udev_list_entry_add(&match_list
, line
, NULL
);
511 err
= insert_data(trie
, &match_list
, line
, filename
);
520 udev_list_cleanup(&match_list
);
524 if (line
[0] != ' ') {
525 log_error("Error, DATA expected but got '%s' in '%s':", line
, filename
);
528 udev_list_cleanup(&match_list
);
532 err
= insert_data(trie
, &match_list
, line
, filename
);
540 udev_list_cleanup(&match_list
);
544 static void help(void) {
545 printf("%s hwdb [OPTIONS]\n\n"
546 " -h --help Print this message\n"
547 " -V --version Print version of the program\n"
548 " -u --update Update the hardware database\n"
549 " -s --strict When updating, return non-zero exit value on any parsing error\n"
550 " --usr Generate in " UDEVLIBEXECDIR
" instead of /etc/udev\n"
551 " -t --test=MODALIAS Query database and print result\n"
552 " -r --root=PATH Alternative root path in the filesystem\n\n"
554 "The sub-command 'hwdb' is deprecated, and is left for backwards compatibility.\n"
555 "Please use systemd-hwdb instead.\n"
556 , program_invocation_short_name
);
559 static int adm_hwdb(struct udev
*udev
, int argc
, char *argv
[]) {
564 static const struct option options
[] = {
565 { "update", no_argument
, NULL
, 'u' },
566 { "usr", no_argument
, NULL
, ARG_USR
},
567 { "strict", no_argument
, NULL
, 's' },
568 { "test", required_argument
, NULL
, 't' },
569 { "root", required_argument
, NULL
, 'r' },
570 { "version", no_argument
, NULL
, 'V' },
571 { "help", no_argument
, NULL
, 'h' },
574 const char *test
= NULL
;
575 const char *root
= "";
576 const char *hwdb_bin_dir
= "/etc/udev";
578 struct trie
*trie
= NULL
;
580 int rc
= EXIT_SUCCESS
;
583 while ((c
= getopt_long(argc
, argv
, "ust:r:Vh", options
, NULL
)) >= 0)
589 hwdb_bin_dir
= UDEVLIBEXECDIR
;
609 assert_not_reached("Unknown option");
612 if (!update
&& !test
) {
613 log_error("Either --update or --test must be used");
619 _cleanup_free_
char *hwdb_bin
= NULL
;
621 trie
= new0(struct trie
, 1);
628 trie
->strings
= strbuf_new();
629 if (!trie
->strings
) {
635 trie
->root
= new0(struct trie_node
, 1);
642 err
= conf_files_list_strv(&files
, ".hwdb", root
, 0, conf_file_dirs
);
644 log_error_errno(err
, "failed to enumerate hwdb files: %m");
648 STRV_FOREACH(f
, files
) {
649 log_debug("reading file '%s'", *f
);
650 if (import_file(udev
, trie
, *f
) < 0 && strict
)
655 strbuf_complete(trie
->strings
);
657 log_debug("=== trie in-memory ===");
658 log_debug("nodes: %8zu bytes (%8zu)",
659 trie
->nodes_count
* sizeof(struct trie_node
), trie
->nodes_count
);
660 log_debug("children arrays: %8zu bytes (%8zu)",
661 trie
->children_count
* sizeof(struct trie_child_entry
), trie
->children_count
);
662 log_debug("values arrays: %8zu bytes (%8zu)",
663 trie
->values_count
* sizeof(struct trie_value_entry
), trie
->values_count
);
664 log_debug("strings: %8zu bytes",
666 log_debug("strings incoming: %8zu bytes (%8zu)",
667 trie
->strings
->in_len
, trie
->strings
->in_count
);
668 log_debug("strings dedup'ed: %8zu bytes (%8zu)",
669 trie
->strings
->dedup_len
, trie
->strings
->dedup_count
);
671 hwdb_bin
= strjoin(root
, "/", hwdb_bin_dir
, "/hwdb.bin");
677 mkdir_parents_label(hwdb_bin
, 0755);
679 err
= trie_store(trie
, hwdb_bin
);
681 log_error_errno(err
, "Failure writing database %s: %m", hwdb_bin
);
685 (void) label_fix(hwdb_bin
, 0);
689 _cleanup_(sd_hwdb_unrefp
) sd_hwdb
*hwdb
= NULL
;
692 r
= sd_hwdb_new(&hwdb
);
694 const char *key
, *value
;
696 SD_HWDB_FOREACH_PROPERTY(hwdb
, test
, key
, value
)
697 printf("%s=%s\n", key
, value
);
703 trie_node_cleanup(trie
->root
);
705 strbuf_cleanup(trie
->strings
);
711 const struct udevadm_cmd udevadm_hwdb
= {