]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/udev/udevadm-hwdb.c
catalog: make sure strings are terminated
[thirdparty/systemd.git] / src / udev / udevadm-hwdb.c
CommitLineData
796b06c2
KS
1/***
2 This file is part of systemd.
3
1298001e 4 Copyright 2012 Kay Sievers <kay@vrfy.org>
796b06c2
KS
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18***/
19
20#include <stdlib.h>
21#include <unistd.h>
22#include <getopt.h>
23#include <string.h>
24
25#include "util.h"
26#include "strbuf.h"
27#include "conf-files.h"
28
29#include "udev.h"
2001208c 30#include "libudev-hwdb-def.h"
796b06c2
KS
31
32/*
33 * Generic udev properties, key/value database based on modalias strings.
34 * Uses a Patricia/radix trie to index all matches for efficient lookup.
35 */
36
37static const char * const conf_file_dirs[] = {
2001208c 38 "/etc/udev/hwdb.d",
796b06c2
KS
39 UDEVLIBEXECDIR "/hwdb.d",
40 NULL
41};
42
43/* in-memory trie objects */
44struct trie {
45 struct trie_node *root;
46 struct strbuf *strings;
47
48 size_t nodes_count;
49 size_t children_count;
50 size_t values_count;
51};
52
53struct trie_node {
54 /* prefix, common part for all children of this node */
55 size_t prefix_off;
56
57 /* sorted array of pointers to children nodes */
58 struct trie_child_entry *children;
59 uint8_t children_count;
60
61 /* sorted array of key/value pairs */
62 struct trie_value_entry *values;
63 size_t values_count;
64};
65
66/* children array item with char (0-255) index */
67struct trie_child_entry {
68 uint8_t c;
69 struct trie_node *child;
70};
71
72/* value array item with key/value pairs */
73struct trie_value_entry {
74 size_t key_off;
75 size_t value_off;
76};
77
78static int trie_children_cmp(const void *v1, const void *v2) {
79 const struct trie_child_entry *n1 = v1;
80 const struct trie_child_entry *n2 = v2;
81
82 return n1->c - n2->c;
83}
84
85static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) {
86 struct trie_child_entry *child;
87 int err = 0;
88
89 /* extend array, add new entry, sort for bisection */
90 child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry));
91 if (!child) {
92 err = -ENOMEM;
93 goto out;
94 }
95 node->children = child;
96 trie->children_count++;
97 node->children[node->children_count].c = c;
98 node->children[node->children_count].child = node_child;
99 node->children_count++;
100 qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
101 trie->nodes_count++;
102out:
103 return err;
104}
105
106static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) {
107 struct trie_child_entry *child;
108 struct trie_child_entry search;
109
110 search.c = c;
111 child = bsearch(&search, node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
112 if (child)
113 return child->child;
114 return NULL;
115}
116
117static void trie_node_cleanup(struct trie_node *node) {
118 size_t i;
119
120 for (i = 0; i < node->children_count; i++)
121 trie_node_cleanup(node->children[i].child);
122 free(node->children);
123 free(node->values);
124 free(node);
125}
126
127static int trie_values_cmp(const void *v1, const void *v2, void *arg) {
128 const struct trie_value_entry *val1 = v1;
129 const struct trie_value_entry *val2 = v2;
130 struct trie *trie = arg;
131
132 return strcmp(trie->strings->buf + val1->key_off,
133 trie->strings->buf + val2->key_off);
134}
135
136static int trie_node_add_value(struct trie *trie, struct trie_node *node,
137 const char *key, const char *value) {
c225f2ff 138 ssize_t k, v;
796b06c2 139 struct trie_value_entry *val;
796b06c2
KS
140
141 k = strbuf_add_string(trie->strings, key, strlen(key));
c225f2ff
KS
142 if (k < 0)
143 return k;
796b06c2 144 v = strbuf_add_string(trie->strings, value, strlen(value));
c225f2ff
KS
145 if (v < 0)
146 return v;
147
148 if (node->values_count) {
149 struct trie_value_entry search = {
150 .key_off = k,
151 .value_off = v,
152 };
153
154 val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
155 if (val) {
156 /* replace existing earlier key with new value */
157 val->value_off = v;
158 return 0;
159 }
796b06c2
KS
160 }
161
162 /* extend array, add new entry, sort for bisection */
163 val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry));
164 if (!val)
165 return -ENOMEM;
166 trie->values_count++;
167 node->values = val;
168 node->values[node->values_count].key_off = k;
169 node->values[node->values_count].value_off = v;
170 node->values_count++;
171 qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
172 return 0;
173}
174
175static int trie_insert(struct trie *trie, struct trie_node *node, const char *search,
176 const char *key, const char *value) {
177 size_t i = 0;
178 int err = 0;
179
180 for (;;) {
181 size_t p;
182 uint8_t c;
183 struct trie_node *child;
184
185 for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) {
186 char *s;
187 ssize_t off;
188
189 if (c == search[i + p])
190 continue;
191
192 /* split node */
193 child = calloc(sizeof(struct trie_node), 1);
194 if (!child) {
195 err = -ENOMEM;
196 goto out;
197 }
198
199 /* move values from parent to child */
200 child->prefix_off = node->prefix_off + p+1;
201 child->children = node->children;
202 child->children_count = node->children_count;
203 child->values = node->values;
204 child->values_count = node->values_count;
205
206 /* update parent; use strdup() because the source gets realloc()d */
207 s = strndup(trie->strings->buf + node->prefix_off, p);
208 if (!s) {
209 err = -ENOMEM;
210 goto out;
211 }
212 off = strbuf_add_string(trie->strings, s, p);
213 free(s);
214 if (off < 0) {
215 err = off;
216 goto out;
217 }
218 node->prefix_off = off;
219 node->children = NULL;
220 node->children_count = 0;
221 node->values = NULL;
222 node->values_count = 0;
223 err = node_add_child(trie, node, child, c);
224 if (err)
225 goto out;
226 break;
227 }
228 i += p;
229
230 c = search[i];
231 if (c == '\0')
232 return trie_node_add_value(trie, node, key, value);
233
234 child = node_lookup(node, c);
235 if (!child) {
236 ssize_t off;
237
238 /* new child */
239 child = calloc(sizeof(struct trie_node), 1);
240 if (!child) {
241 err = -ENOMEM;
242 goto out;
243 }
244 off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1));
245 if (off < 0) {
246 err = off;
247 goto out;
248 }
249 child->prefix_off = off;
250 err = node_add_child(trie, node, child, c);
251 if (err)
252 goto out;
253 return trie_node_add_value(trie, child, key, value);
254 }
255
256 node = child;
257 i++;
258 }
259out:
260 return err;
261}
262
263struct trie_f {
264 FILE *f;
265 struct trie *trie;
266 uint64_t strings_off;
267
268 uint64_t nodes_count;
269 uint64_t children_count;
270 uint64_t values_count;
271};
272
273/* calculate the storage space for the nodes, children arrays, value arrays */
274static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) {
275 uint64_t i;
276
277 for (i = 0; i < node->children_count; i++)
278 trie_store_nodes_size(trie, node->children[i].child);
279
280 trie->strings_off += sizeof(struct trie_node_f);
281 for (i = 0; i < node->children_count; i++)
282 trie->strings_off += sizeof(struct trie_child_entry_f);
283 for (i = 0; i < node->values_count; i++)
284 trie->strings_off += sizeof(struct trie_value_entry_f);
285}
286
287static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) {
288 uint64_t i;
289 struct trie_node_f n = {
290 .prefix_off = htole64(trie->strings_off + node->prefix_off),
291 .children_count = node->children_count,
292 .values_count = htole64(node->values_count),
293 };
2001208c 294 struct trie_child_entry_f *children = NULL;
796b06c2
KS
295 int64_t node_off;
296
297 if (node->children_count) {
298 children = new0(struct trie_child_entry_f, node->children_count);
299 if (!children)
300 return -ENOMEM;
301 }
302
303 /* post-order recursion */
304 for (i = 0; i < node->children_count; i++) {
305 int64_t child_off;
306
307 child_off = trie_store_nodes(trie, node->children[i].child);
308 if (child_off < 0)
309 return child_off;
310 children[i].c = node->children[i].c;
311 children[i].child_off = htole64(child_off);
312 }
313
314 /* write node */
315 node_off = ftello(trie->f);
316 fwrite(&n, sizeof(struct trie_node_f), 1, trie->f);
317 trie->nodes_count++;
318
319 /* append children array */
320 if (node->children_count) {
321 fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f);
322 trie->children_count += node->children_count;
323 free(children);
324 }
325
326 /* append values array */
327 for (i = 0; i < node->values_count; i++) {
328 struct trie_value_entry_f v = {
329 .key_off = htole64(trie->strings_off + node->values[i].key_off),
330 .value_off = htole64(trie->strings_off + node->values[i].value_off),
331 };
332
333 fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f);
334 trie->values_count++;
335 }
336
337 return node_off;
338}
339
340static int trie_store(struct trie *trie, const char *filename) {
341 struct trie_f t = {
342 .trie = trie,
343 };
344 char *filename_tmp;
345 int64_t pos;
346 int64_t root_off;
347 int64_t size;
348 struct trie_header_f h = {
349 .signature = HWDB_SIG,
350 .tool_version = htole64(atoi(VERSION)),
351 .header_size = htole64(sizeof(struct trie_header_f)),
352 .node_size = htole64(sizeof(struct trie_node_f)),
353 .child_entry_size = htole64(sizeof(struct trie_child_entry_f)),
354 .value_entry_size = htole64(sizeof(struct trie_value_entry_f)),
355 };
356 int err;
357
358 /* calculate size of header, nodes, children entries, value entries */
359 t.strings_off = sizeof(struct trie_header_f);
360 trie_store_nodes_size(&t, trie->root);
361
362 err = fopen_temporary(filename , &t.f, &filename_tmp);
363 if (err < 0)
364 return err;
365 fchmod(fileno(t.f), 0444);
366
367 /* write nodes */
368 fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET);
369 root_off = trie_store_nodes(&t, trie->root);
370 h.nodes_root_off = htole64(root_off);
371 pos = ftello(t.f);
372 h.nodes_len = htole64(pos - sizeof(struct trie_header_f));
373
374 /* write string buffer */
375 fwrite(trie->strings->buf, trie->strings->len, 1, t.f);
376 h.strings_len = htole64(trie->strings->len);
377
378 /* write header */
379 size = ftello(t.f);
380 h.file_size = htole64(size);
381 fseeko(t.f, 0, SEEK_SET);
382 fwrite(&h, sizeof(struct trie_header_f), 1, t.f);
383 err = ferror(t.f);
384 if (err)
385 err = -errno;
386 fclose(t.f);
387 if (err < 0 || rename(filename_tmp, filename) < 0) {
388 unlink(filename_tmp);
389 goto out;
390 }
391
392 log_debug("=== trie on-disk ===\n");
2001208c 393 log_debug("size: %8llu bytes\n", (unsigned long long)size);
796b06c2 394 log_debug("header: %8zu bytes\n", sizeof(struct trie_header_f));
2001208c
KS
395 log_debug("nodes: %8llu bytes (%8llu)\n",
396 (unsigned long long)t.nodes_count * sizeof(struct trie_node_f), (unsigned long long)t.nodes_count);
397 log_debug("child pointers: %8llu bytes (%8llu)\n",
398 (unsigned long long)t.children_count * sizeof(struct trie_child_entry_f), (unsigned long long)t.children_count);
399 log_debug("value pointers: %8llu bytes (%8llu)\n",
400 (unsigned long long)t.values_count * sizeof(struct trie_value_entry_f), (unsigned long long)t.values_count);
401 log_debug("string store: %8llu bytes\n", (unsigned long long)trie->strings->len);
796b06c2
KS
402 log_debug("strings start: %8llu\n", (unsigned long long) t.strings_off);
403out:
404 free(filename_tmp);
405 return err;
406}
407
408static int import_file(struct trie *trie, const char *filename) {
409 FILE *f;
410 char line[LINE_MAX];
411 char match[LINE_MAX];
2001208c 412 char cond[LINE_MAX];
796b06c2
KS
413
414 f = fopen(filename, "re");
415 if (f == NULL)
416 return -errno;
417
418 match[0] = '\0';
2001208c 419 cond[0] = '\0';
796b06c2
KS
420 while (fgets(line, sizeof(line), f)) {
421 size_t len;
422
423 if (line[0] == '#')
424 continue;
425
426 /* new line, new record */
427 if (line[0] == '\n') {
428 match[0] = '\0';
2001208c 429 cond[0] = '\0';
796b06c2
KS
430 continue;
431 }
432
433 /* remove newline */
434 len = strlen(line);
435 if (len < 2)
436 continue;
437 line[len-1] = '\0';
438
439 /* start of new record */
440 if (match[0] == '\0') {
441 strcpy(match, line);
2001208c 442 cond[0] = '\0';
796b06c2
KS
443 continue;
444 }
445
2001208c
KS
446 if (line[0] == '+') {
447 strcpy(cond, line);
448 continue;
449 }
450
451 /* TODO: support +; skip the entire record until we support it */
452 if (cond[0] != '\0')
453 continue;
454
796b06c2
KS
455 /* value lines */
456 if (line[0] == ' ') {
457 char *value;
458
459 value = strchr(line, '=');
460 if (!value)
461 continue;
462 value[0] = '\0';
463 value++;
464 trie_insert(trie, trie->root, match, line, value);
465 }
466 }
467 fclose(f);
468 return 0;
469}
470
471static void help(void) {
e32a4e1e 472 printf("Usage: udevadm hwdb OPTIONS\n"
796b06c2 473 " --update update the hardware database\n"
3e49f2ec
KS
474 " --test=<modalias> query database and print result\n"
475 " --root=<path> alternative root path in the filesystem\n"
796b06c2
KS
476 " --help\n\n");
477}
478
479static int adm_hwdb(struct udev *udev, int argc, char *argv[]) {
480 static const struct option options[] = {
481 { "update", no_argument, NULL, 'u' },
3e49f2ec 482 { "root", required_argument, NULL, 'r' },
23b72453 483 { "test", required_argument, NULL, 't' },
796b06c2
KS
484 { "help", no_argument, NULL, 'h' },
485 {}
486 };
23b72453 487 const char *test = NULL;
3e49f2ec 488 const char *root = "";
796b06c2 489 bool update = false;
23b72453 490 struct trie *trie = NULL;
796b06c2
KS
491 int err;
492 int rc = EXIT_SUCCESS;
493
494 for (;;) {
495 int option;
496
3e49f2ec 497 option = getopt_long(argc, argv, "ut:r:h", options, NULL);
796b06c2
KS
498 if (option == -1)
499 break;
500
501 switch (option) {
502 case 'u':
503 update = true;
504 break;
23b72453
KS
505 case 't':
506 test = optarg;
507 break;
3e49f2ec
KS
508 case 'r':
509 root = optarg;
510 break;
796b06c2
KS
511 case 'h':
512 help();
513 return EXIT_SUCCESS;
514 }
515 }
516
23b72453 517 if (!update && !test) {
796b06c2
KS
518 help();
519 return EXIT_SUCCESS;
520 }
521
23b72453
KS
522 if (update) {
523 char **files, **f;
3e49f2ec 524 _cleanup_free_ char *hwdb_bin = NULL;
796b06c2 525
23b72453
KS
526 trie = calloc(sizeof(struct trie), 1);
527 if (!trie) {
528 rc = EXIT_FAILURE;
529 goto out;
530 }
796b06c2 531
23b72453
KS
532 /* string store */
533 trie->strings = strbuf_new();
534 if (!trie->strings) {
535 rc = EXIT_FAILURE;
536 goto out;
537 }
796b06c2 538
23b72453
KS
539 /* index */
540 trie->root = calloc(sizeof(struct trie_node), 1);
541 if (!trie->root) {
542 rc = EXIT_FAILURE;
543 goto out;
544 }
545 trie->nodes_count++;
546
3e49f2ec 547 err = conf_files_list_strv(&files, ".hwdb", root, (const char **)conf_file_dirs);
23b72453
KS
548 if (err < 0) {
549 log_error("failed to enumerate hwdb files: %s\n", strerror(-err));
550 rc = EXIT_FAILURE;
551 goto out;
552 }
553 STRV_FOREACH(f, files) {
554 log_debug("reading file '%s'", *f);
555 import_file(trie, *f);
556 }
557 strv_free(files);
558
559 strbuf_complete(trie->strings);
560
561 log_debug("=== trie in-memory ===\n");
3a4431ef
KS
562 log_debug("nodes: %8zu bytes (%8zu)\n",
563 trie->nodes_count * sizeof(struct trie_node), trie->nodes_count);
564 log_debug("children arrays: %8zu bytes (%8zu)\n",
565 trie->children_count * sizeof(struct trie_child_entry), trie->children_count);
566 log_debug("values arrays: %8zu bytes (%8zu)\n",
567 trie->values_count * sizeof(struct trie_value_entry), trie->values_count);
568 log_debug("strings: %8zu bytes\n",
569 trie->strings->len);
570 log_debug("strings incoming: %8zu bytes (%8zu)\n",
571 trie->strings->in_len, trie->strings->in_count);
572 log_debug("strings dedup'ed: %8zu bytes (%8zu)\n",
573 trie->strings->dedup_len, trie->strings->dedup_count);
23b72453 574
3e49f2ec
KS
575 if (asprintf(&hwdb_bin, "%s/etc/udev/hwdb.bin", root) < 0) {
576 rc = EXIT_FAILURE;
577 goto out;
578 }
579 mkdir_parents(hwdb_bin, 0755);
580 err = trie_store(trie, hwdb_bin);
23b72453 581 if (err < 0) {
3e49f2ec 582 log_error("Failure writing database %s: %s", hwdb_bin, strerror(-err));
23b72453
KS
583 rc = EXIT_FAILURE;
584 }
796b06c2 585 }
23b72453
KS
586
587 if (test) {
588 struct udev_hwdb *hwdb = udev_hwdb_new(udev);
589
590 if (hwdb) {
591 struct udev_list_entry *entry;
592
593 udev_list_entry_foreach(entry, udev_hwdb_get_properties_list_entry(hwdb, test, 0))
594 printf("%s=%s\n", udev_list_entry_get_name(entry), udev_list_entry_get_value(entry));
595 hwdb = udev_hwdb_unref(hwdb);
596 }
796b06c2 597 }
796b06c2 598out:
23b72453
KS
599 if (trie) {
600 if (trie->root)
601 trie_node_cleanup(trie->root);
602 strbuf_cleanup(trie->strings);
603 free(trie);
604 }
796b06c2
KS
605 return rc;
606}
607
608const struct udevadm_cmd udevadm_hwdb = {
609 .name = "hwdb",
610 .cmd = adm_hwdb,
611 .help = "maintain the hardware database index",
612};