]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/udevadm-hwdb.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2012 Kay Sievers <kay@vrfy.org>
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include "alloc-util.h"
27 #include "conf-files.h"
30 #include "hwdb-internal.h"
31 #include "hwdb-util.h"
35 #include "string-util.h"
40 * Generic udev properties, key/value database based on modalias strings.
41 * Uses a Patricia/radix trie to index all matches for efficient lookup.
44 static const char * const conf_file_dirs
[] = {
46 UDEVLIBEXECDIR
"/hwdb.d",
50 /* in-memory trie objects */
52 struct trie_node
*root
;
53 struct strbuf
*strings
;
56 size_t children_count
;
61 /* prefix, common part for all children of this node */
64 /* sorted array of pointers to children nodes */
65 struct trie_child_entry
*children
;
66 uint8_t children_count
;
68 /* sorted array of key/value pairs */
69 struct trie_value_entry
*values
;
73 /* children array item with char (0-255) index */
74 struct trie_child_entry
{
76 struct trie_node
*child
;
79 /* value array item with key/value pairs */
80 struct trie_value_entry
{
85 static int trie_children_cmp(const void *v1
, const void *v2
) {
86 const struct trie_child_entry
*n1
= v1
;
87 const struct trie_child_entry
*n2
= v2
;
92 static int node_add_child(struct trie
*trie
, struct trie_node
*node
, struct trie_node
*node_child
, uint8_t c
) {
93 struct trie_child_entry
*child
;
95 /* extend array, add new entry, sort for bisection */
96 child
= realloc(node
->children
, (node
->children_count
+ 1) * sizeof(struct trie_child_entry
));
100 node
->children
= child
;
101 trie
->children_count
++;
102 node
->children
[node
->children_count
].c
= c
;
103 node
->children
[node
->children_count
].child
= node_child
;
104 node
->children_count
++;
105 qsort(node
->children
, node
->children_count
, sizeof(struct trie_child_entry
), trie_children_cmp
);
111 static struct trie_node
*node_lookup(const struct trie_node
*node
, uint8_t c
) {
112 struct trie_child_entry
*child
;
113 struct trie_child_entry search
;
116 child
= bsearch(&search
, node
->children
, node
->children_count
, sizeof(struct trie_child_entry
), trie_children_cmp
);
122 static void trie_node_cleanup(struct trie_node
*node
) {
125 for (i
= 0; i
< node
->children_count
; i
++)
126 trie_node_cleanup(node
->children
[i
].child
);
127 free(node
->children
);
132 static int trie_values_cmp(const void *v1
, const void *v2
, void *arg
) {
133 const struct trie_value_entry
*val1
= v1
;
134 const struct trie_value_entry
*val2
= v2
;
135 struct trie
*trie
= arg
;
137 return strcmp(trie
->strings
->buf
+ val1
->key_off
,
138 trie
->strings
->buf
+ val2
->key_off
);
141 static int trie_node_add_value(struct trie
*trie
, struct trie_node
*node
,
142 const char *key
, const char *value
) {
144 struct trie_value_entry
*val
;
146 k
= strbuf_add_string(trie
->strings
, key
, strlen(key
));
149 v
= strbuf_add_string(trie
->strings
, value
, strlen(value
));
153 if (node
->values_count
) {
154 struct trie_value_entry search
= {
159 val
= xbsearch_r(&search
, node
->values
, node
->values_count
, sizeof(struct trie_value_entry
), trie_values_cmp
, trie
);
161 /* replace existing earlier key with new value */
167 /* extend array, add new entry, sort for bisection */
168 val
= realloc(node
->values
, (node
->values_count
+ 1) * sizeof(struct trie_value_entry
));
171 trie
->values_count
++;
173 node
->values
[node
->values_count
].key_off
= k
;
174 node
->values
[node
->values_count
].value_off
= v
;
175 node
->values_count
++;
176 qsort_r(node
->values
, node
->values_count
, sizeof(struct trie_value_entry
), trie_values_cmp
, trie
);
180 static int trie_insert(struct trie
*trie
, struct trie_node
*node
, const char *search
,
181 const char *key
, const char *value
) {
188 struct trie_node
*child
;
190 for (p
= 0; (c
= trie
->strings
->buf
[node
->prefix_off
+ p
]); p
++) {
191 _cleanup_free_
char *s
= NULL
;
193 _cleanup_free_
struct trie_node
*new_child
= NULL
;
195 if (c
== search
[i
+ p
])
199 new_child
= new0(struct trie_node
, 1);
203 /* move values from parent to child */
204 new_child
->prefix_off
= node
->prefix_off
+ p
+1;
205 new_child
->children
= node
->children
;
206 new_child
->children_count
= node
->children_count
;
207 new_child
->values
= node
->values
;
208 new_child
->values_count
= node
->values_count
;
210 /* update parent; use strdup() because the source gets realloc()d */
211 s
= strndup(trie
->strings
->buf
+ node
->prefix_off
, p
);
215 off
= strbuf_add_string(trie
->strings
, s
, p
);
219 node
->prefix_off
= off
;
220 node
->children
= NULL
;
221 node
->children_count
= 0;
223 node
->values_count
= 0;
224 err
= node_add_child(trie
, node
, new_child
, c
);
228 new_child
= NULL
; /* avoid cleanup */
235 return trie_node_add_value(trie
, node
, key
, value
);
237 child
= node_lookup(node
, c
);
242 child
= new0(struct trie_node
, 1);
246 off
= strbuf_add_string(trie
->strings
, search
+ i
+1, strlen(search
+ i
+1));
252 child
->prefix_off
= off
;
253 err
= node_add_child(trie
, node
, child
, c
);
259 return trie_node_add_value(trie
, child
, key
, value
);
270 uint64_t strings_off
;
272 uint64_t nodes_count
;
273 uint64_t children_count
;
274 uint64_t values_count
;
277 /* calculate the storage space for the nodes, children arrays, value arrays */
278 static void trie_store_nodes_size(struct trie_f
*trie
, struct trie_node
*node
) {
281 for (i
= 0; i
< node
->children_count
; i
++)
282 trie_store_nodes_size(trie
, node
->children
[i
].child
);
284 trie
->strings_off
+= sizeof(struct trie_node_f
);
285 for (i
= 0; i
< node
->children_count
; i
++)
286 trie
->strings_off
+= sizeof(struct trie_child_entry_f
);
287 for (i
= 0; i
< node
->values_count
; i
++)
288 trie
->strings_off
+= sizeof(struct trie_value_entry_f
);
291 static int64_t trie_store_nodes(struct trie_f
*trie
, struct trie_node
*node
) {
293 struct trie_node_f n
= {
294 .prefix_off
= htole64(trie
->strings_off
+ node
->prefix_off
),
295 .children_count
= node
->children_count
,
296 .values_count
= htole64(node
->values_count
),
298 struct trie_child_entry_f
*children
= NULL
;
301 if (node
->children_count
) {
302 children
= new0(struct trie_child_entry_f
, node
->children_count
);
307 /* post-order recursion */
308 for (i
= 0; i
< node
->children_count
; i
++) {
311 child_off
= trie_store_nodes(trie
, node
->children
[i
].child
);
316 children
[i
].c
= node
->children
[i
].c
;
317 children
[i
].child_off
= htole64(child_off
);
321 node_off
= ftello(trie
->f
);
322 fwrite(&n
, sizeof(struct trie_node_f
), 1, trie
->f
);
325 /* append children array */
326 if (node
->children_count
) {
327 fwrite(children
, sizeof(struct trie_child_entry_f
), node
->children_count
, trie
->f
);
328 trie
->children_count
+= node
->children_count
;
332 /* append values array */
333 for (i
= 0; i
< node
->values_count
; i
++) {
334 struct trie_value_entry_f v
= {
335 .key_off
= htole64(trie
->strings_off
+ node
->values
[i
].key_off
),
336 .value_off
= htole64(trie
->strings_off
+ node
->values
[i
].value_off
),
339 fwrite(&v
, sizeof(struct trie_value_entry_f
), 1, trie
->f
);
340 trie
->values_count
++;
346 static int trie_store(struct trie
*trie
, const char *filename
) {
350 _cleanup_free_
char *filename_tmp
= NULL
;
354 struct trie_header_f h
= {
355 .signature
= HWDB_SIG
,
356 .tool_version
= htole64(atoi(PACKAGE_VERSION
)),
357 .header_size
= htole64(sizeof(struct trie_header_f
)),
358 .node_size
= htole64(sizeof(struct trie_node_f
)),
359 .child_entry_size
= htole64(sizeof(struct trie_child_entry_f
)),
360 .value_entry_size
= htole64(sizeof(struct trie_value_entry_f
)),
364 /* calculate size of header, nodes, children entries, value entries */
365 t
.strings_off
= sizeof(struct trie_header_f
);
366 trie_store_nodes_size(&t
, trie
->root
);
368 err
= fopen_temporary(filename
, &t
.f
, &filename_tmp
);
371 fchmod(fileno(t
.f
), 0444);
374 if (fseeko(t
.f
, sizeof(struct trie_header_f
), SEEK_SET
) < 0)
376 root_off
= trie_store_nodes(&t
, trie
->root
);
377 h
.nodes_root_off
= htole64(root_off
);
379 h
.nodes_len
= htole64(pos
- sizeof(struct trie_header_f
));
381 /* write string buffer */
382 fwrite(trie
->strings
->buf
, trie
->strings
->len
, 1, t
.f
);
383 h
.strings_len
= htole64(trie
->strings
->len
);
387 h
.file_size
= htole64(size
);
388 if (fseeko(t
.f
, 0, SEEK_SET
< 0))
390 fwrite(&h
, sizeof(struct trie_header_f
), 1, t
.f
);
396 if (fsync(fileno(t
.f
)) < 0)
398 if (rename(filename_tmp
, filename
) < 0)
401 /* write succeeded */
404 log_debug("=== trie on-disk ===");
405 log_debug("size: %8"PRIi64
" bytes", size
);
406 log_debug("header: %8zu bytes", sizeof(struct trie_header_f
));
407 log_debug("nodes: %8"PRIu64
" bytes (%8"PRIu64
")",
408 t
.nodes_count
* sizeof(struct trie_node_f
), t
.nodes_count
);
409 log_debug("child pointers: %8"PRIu64
" bytes (%8"PRIu64
")",
410 t
.children_count
* sizeof(struct trie_child_entry_f
), t
.children_count
);
411 log_debug("value pointers: %8"PRIu64
" bytes (%8"PRIu64
")",
412 t
.values_count
* sizeof(struct trie_value_entry_f
), t
.values_count
);
413 log_debug("string store: %8zu bytes", trie
->strings
->len
);
414 log_debug("strings start: %8"PRIu64
, t
.strings_off
);
421 unlink(filename_tmp
);
425 static int insert_data(struct trie
*trie
, struct udev_list
*match_list
,
426 char *line
, const char *filename
) {
428 struct udev_list_entry
*entry
;
430 value
= strchr(line
, '=');
432 log_error("Error, key/value pair expected but got '%s' in '%s':", line
, filename
);
439 /* libudev requires properties to start with a space */
440 while (isblank(line
[0]) && isblank(line
[1]))
443 if (line
[0] == '\0' || value
[0] == '\0') {
444 log_error("Error, empty key or value '%s' in '%s':", line
, filename
);
448 udev_list_entry_foreach(entry
, udev_list_get_entry(match_list
))
449 trie_insert(trie
, trie
->root
, udev_list_entry_get_name(entry
), line
, value
);
454 static int import_file(struct udev
*udev
, struct trie
*trie
, const char *filename
) {
462 struct udev_list match_list
;
464 udev_list_init(udev
, &match_list
, false);
466 f
= fopen(filename
, "re");
470 while (fgets(line
, sizeof(line
), f
)) {
478 /* strip trailing comment */
479 pos
= strchr(line
, '#');
483 /* strip trailing whitespace */
485 while (len
> 0 && isspace(line
[len
-1]))
494 if (line
[0] == ' ') {
495 log_error("Error, MATCH expected but got '%s' in '%s':", line
, filename
);
499 /* start of record, first match */
501 udev_list_entry_add(&match_list
, line
, NULL
);
506 log_error("Error, DATA expected but got empty line in '%s':", filename
);
508 udev_list_cleanup(&match_list
);
513 if (line
[0] != ' ') {
514 udev_list_entry_add(&match_list
, line
, NULL
);
520 insert_data(trie
, &match_list
, line
, filename
);
527 udev_list_cleanup(&match_list
);
531 if (line
[0] != ' ') {
532 log_error("Error, DATA expected but got '%s' in '%s':", line
, filename
);
534 udev_list_cleanup(&match_list
);
538 insert_data(trie
, &match_list
, line
, filename
);
544 udev_list_cleanup(&match_list
);
548 static void help(void) {
549 printf("Usage: udevadm hwdb OPTIONS\n"
550 " -u,--update update the hardware database\n"
551 " --usr generate in " UDEVLIBEXECDIR
" instead of /etc/udev\n"
552 " -t,--test=MODALIAS query database and print result\n"
553 " -r,--root=PATH alternative root path in the filesystem\n"
557 static int adm_hwdb(struct udev
*udev
, int argc
, char *argv
[]) {
562 static const struct option options
[] = {
563 { "update", no_argument
, NULL
, 'u' },
564 { "usr", no_argument
, NULL
, ARG_USR
},
565 { "test", required_argument
, NULL
, 't' },
566 { "root", required_argument
, NULL
, 'r' },
567 { "help", no_argument
, NULL
, 'h' },
570 const char *test
= NULL
;
571 const char *root
= "";
572 const char *hwdb_bin_dir
= "/etc/udev";
574 struct trie
*trie
= NULL
;
576 int rc
= EXIT_SUCCESS
;
578 while ((c
= getopt_long(argc
, argv
, "ut:r:h", options
, NULL
)) >= 0)
584 hwdb_bin_dir
= UDEVLIBEXECDIR
;
598 assert_not_reached("Unknown option");
601 if (!update
&& !test
) {
602 log_error("Either --update or --test must be used");
608 _cleanup_free_
char *hwdb_bin
= NULL
;
610 trie
= new0(struct trie
, 1);
617 trie
->strings
= strbuf_new();
618 if (!trie
->strings
) {
624 trie
->root
= new0(struct trie_node
, 1);
631 err
= conf_files_list_strv(&files
, ".hwdb", root
, 0, conf_file_dirs
);
633 log_error_errno(err
, "failed to enumerate hwdb files: %m");
637 STRV_FOREACH(f
, files
) {
638 log_debug("reading file '%s'", *f
);
639 import_file(udev
, trie
, *f
);
643 strbuf_complete(trie
->strings
);
645 log_debug("=== trie in-memory ===");
646 log_debug("nodes: %8zu bytes (%8zu)",
647 trie
->nodes_count
* sizeof(struct trie_node
), trie
->nodes_count
);
648 log_debug("children arrays: %8zu bytes (%8zu)",
649 trie
->children_count
* sizeof(struct trie_child_entry
), trie
->children_count
);
650 log_debug("values arrays: %8zu bytes (%8zu)",
651 trie
->values_count
* sizeof(struct trie_value_entry
), trie
->values_count
);
652 log_debug("strings: %8zu bytes",
654 log_debug("strings incoming: %8zu bytes (%8zu)",
655 trie
->strings
->in_len
, trie
->strings
->in_count
);
656 log_debug("strings dedup'ed: %8zu bytes (%8zu)",
657 trie
->strings
->dedup_len
, trie
->strings
->dedup_count
);
659 hwdb_bin
= strjoin(root
, "/", hwdb_bin_dir
, "/hwdb.bin");
665 mkdir_parents_label(hwdb_bin
, 0755);
667 err
= trie_store(trie
, hwdb_bin
);
669 log_error_errno(err
, "Failure writing database %s: %m", hwdb_bin
);
673 label_fix(hwdb_bin
, false, false);
677 _cleanup_(sd_hwdb_unrefp
) sd_hwdb
*hwdb
= NULL
;
680 r
= sd_hwdb_new(&hwdb
);
682 const char *key
, *value
;
684 SD_HWDB_FOREACH_PROPERTY(hwdb
, test
, key
, value
)
685 printf("%s=%s\n", key
, value
);
691 trie_node_cleanup(trie
->root
);
692 strbuf_cleanup(trie
->strings
);
698 const struct udevadm_cmd udevadm_hwdb
= {