]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/hwdb/hwdb.c
Merge pull request #4448 from msoltyspl/vcfix
[thirdparty/systemd.git] / src / hwdb / hwdb.c
CommitLineData
65eb4378
TG
1/***
2 This file is part of systemd.
3
4 Copyright 2012 Kay Sievers <kay@vrfy.org>
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
3f6fd1ba 20#include <ctype.h>
65eb4378 21#include <getopt.h>
3f6fd1ba 22#include <stdlib.h>
65eb4378 23#include <string.h>
65eb4378 24
b5efdb8a 25#include "alloc-util.h"
65eb4378 26#include "conf-files.h"
0d39fa9c
LP
27#include "fd-util.h"
28#include "fileio.h"
f4f15635 29#include "fs-util.h"
65eb4378
TG
30#include "hwdb-internal.h"
31#include "hwdb-util.h"
ea683512 32#include "label.h"
3f6fd1ba 33#include "mkdir.h"
ea683512 34#include "selinux-util.h"
3f6fd1ba 35#include "strbuf.h"
07630cea 36#include "string-util.h"
3f6fd1ba
LP
37#include "strv.h"
38#include "util.h"
39#include "verbs.h"
65eb4378
TG
40
41/*
42 * Generic udev properties, key/value database based on modalias strings.
43 * Uses a Patricia/radix trie to index all matches for efficient lookup.
44 */
45
46static const char *arg_hwdb_bin_dir = "/etc/udev";
47static const char *arg_root = "";
48
49static const char * const conf_file_dirs[] = {
50 "/etc/udev/hwdb.d",
51 UDEVLIBEXECDIR "/hwdb.d",
52 NULL
53};
54
55/* in-memory trie objects */
56struct trie {
57 struct trie_node *root;
58 struct strbuf *strings;
59
60 size_t nodes_count;
61 size_t children_count;
62 size_t values_count;
63};
64
65struct trie_node {
66 /* prefix, common part for all children of this node */
67 size_t prefix_off;
68
69 /* sorted array of pointers to children nodes */
70 struct trie_child_entry *children;
71 uint8_t children_count;
72
73 /* sorted array of key/value pairs */
74 struct trie_value_entry *values;
75 size_t values_count;
76};
77
78/* children array item with char (0-255) index */
79struct trie_child_entry {
80 uint8_t c;
81 struct trie_node *child;
82};
83
84/* value array item with key/value pairs */
85struct trie_value_entry {
86 size_t key_off;
87 size_t value_off;
698c5a17
DH
88 size_t filename_off;
89 size_t line_number;
65eb4378
TG
90};
91
92static int trie_children_cmp(const void *v1, const void *v2) {
93 const struct trie_child_entry *n1 = v1;
94 const struct trie_child_entry *n2 = v2;
95
96 return n1->c - n2->c;
97}
98
99static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) {
100 struct trie_child_entry *child;
101
102 /* extend array, add new entry, sort for bisection */
103 child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry));
104 if (!child)
105 return -ENOMEM;
106
107 node->children = child;
108 trie->children_count++;
109 node->children[node->children_count].c = c;
110 node->children[node->children_count].child = node_child;
111 node->children_count++;
112 qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
113 trie->nodes_count++;
114
115 return 0;
116}
117
118static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) {
119 struct trie_child_entry *child;
120 struct trie_child_entry search;
121
122 search.c = c;
123 child = bsearch(&search, node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp);
124 if (child)
125 return child->child;
126 return NULL;
127}
128
129static void trie_node_cleanup(struct trie_node *node) {
130 size_t i;
131
132 for (i = 0; i < node->children_count; i++)
133 trie_node_cleanup(node->children[i].child);
134 free(node->children);
135 free(node->values);
136 free(node);
137}
138
139static void trie_free(struct trie *trie) {
140 if (!trie)
141 return;
142
143 if (trie->root)
144 trie_node_cleanup(trie->root);
145
146 strbuf_cleanup(trie->strings);
147 free(trie);
148}
149
150DEFINE_TRIVIAL_CLEANUP_FUNC(struct trie*, trie_free);
151
152static int trie_values_cmp(const void *v1, const void *v2, void *arg) {
153 const struct trie_value_entry *val1 = v1;
154 const struct trie_value_entry *val2 = v2;
155 struct trie *trie = arg;
156
157 return strcmp(trie->strings->buf + val1->key_off,
158 trie->strings->buf + val2->key_off);
159}
160
161static int trie_node_add_value(struct trie *trie, struct trie_node *node,
698c5a17
DH
162 const char *key, const char *value,
163 const char *filename, size_t line_number) {
164 ssize_t k, v, fn;
65eb4378 165 struct trie_value_entry *val;
698c5a17 166 int r;
65eb4378
TG
167
168 k = strbuf_add_string(trie->strings, key, strlen(key));
169 if (k < 0)
170 return k;
171 v = strbuf_add_string(trie->strings, value, strlen(value));
698c5a17
DH
172 if (v < 0)
173 return v;
174 fn = strbuf_add_string(trie->strings, filename, strlen(filename));
65eb4378
TG
175 if (v < 0)
176 return v;
177
178 if (node->values_count) {
179 struct trie_value_entry search = {
180 .key_off = k,
181 .value_off = v,
182 };
183
184 val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
185 if (val) {
698c5a17
DH
186 /*
187 * At this point we have 2 identical properties on the same match-string. We
188 * strictly order them by filename+line-number, since we know the dynamic
189 * runtime lookup does the same for multiple matching nodes.
190 */
191 r = strcmp(filename, trie->strings->buf + val->filename_off);
192 if (r < 0 ||
193 (r == 0 && line_number < val->line_number))
194 return 0;
195
65eb4378
TG
196 /* replace existing earlier key with new value */
197 val->value_off = v;
698c5a17
DH
198 val->filename_off = fn;
199 val->line_number = line_number;
65eb4378
TG
200 return 0;
201 }
202 }
203
204 /* extend array, add new entry, sort for bisection */
205 val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry));
206 if (!val)
207 return -ENOMEM;
208 trie->values_count++;
209 node->values = val;
210 node->values[node->values_count].key_off = k;
211 node->values[node->values_count].value_off = v;
698c5a17
DH
212 node->values[node->values_count].filename_off = fn;
213 node->values[node->values_count].line_number = line_number;
65eb4378
TG
214 node->values_count++;
215 qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
216 return 0;
217}
218
219static int trie_insert(struct trie *trie, struct trie_node *node, const char *search,
698c5a17
DH
220 const char *key, const char *value,
221 const char *filename, uint64_t line_number) {
65eb4378
TG
222 size_t i = 0;
223 int err = 0;
224
225 for (;;) {
226 size_t p;
227 uint8_t c;
228 struct trie_node *child;
229
230 for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) {
231 _cleanup_free_ char *s = NULL;
232 ssize_t off;
233 _cleanup_free_ struct trie_node *new_child = NULL;
234
235 if (c == search[i + p])
236 continue;
237
238 /* split node */
239 new_child = new0(struct trie_node, 1);
240 if (!new_child)
241 return -ENOMEM;
242
243 /* move values from parent to child */
244 new_child->prefix_off = node->prefix_off + p+1;
245 new_child->children = node->children;
246 new_child->children_count = node->children_count;
247 new_child->values = node->values;
248 new_child->values_count = node->values_count;
249
250 /* update parent; use strdup() because the source gets realloc()d */
251 s = strndup(trie->strings->buf + node->prefix_off, p);
252 if (!s)
253 return -ENOMEM;
254
255 off = strbuf_add_string(trie->strings, s, p);
256 if (off < 0)
257 return off;
258
259 node->prefix_off = off;
260 node->children = NULL;
261 node->children_count = 0;
262 node->values = NULL;
263 node->values_count = 0;
264 err = node_add_child(trie, node, new_child, c);
265 if (err < 0)
266 return err;
267
268 new_child = NULL; /* avoid cleanup */
269 break;
270 }
271 i += p;
272
273 c = search[i];
274 if (c == '\0')
698c5a17 275 return trie_node_add_value(trie, node, key, value, filename, line_number);
65eb4378
TG
276
277 child = node_lookup(node, c);
278 if (!child) {
279 ssize_t off;
280
281 /* new child */
282 child = new0(struct trie_node, 1);
283 if (!child)
284 return -ENOMEM;
285
286 off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1));
287 if (off < 0) {
288 free(child);
289 return off;
290 }
291
292 child->prefix_off = off;
293 err = node_add_child(trie, node, child, c);
294 if (err < 0) {
295 free(child);
296 return err;
297 }
298
698c5a17 299 return trie_node_add_value(trie, child, key, value, filename, line_number);
65eb4378
TG
300 }
301
302 node = child;
303 i++;
304 }
305}
306
307struct trie_f {
308 FILE *f;
309 struct trie *trie;
310 uint64_t strings_off;
311
312 uint64_t nodes_count;
313 uint64_t children_count;
314 uint64_t values_count;
315};
316
317/* calculate the storage space for the nodes, children arrays, value arrays */
318static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) {
319 uint64_t i;
320
321 for (i = 0; i < node->children_count; i++)
322 trie_store_nodes_size(trie, node->children[i].child);
323
324 trie->strings_off += sizeof(struct trie_node_f);
325 for (i = 0; i < node->children_count; i++)
326 trie->strings_off += sizeof(struct trie_child_entry_f);
327 for (i = 0; i < node->values_count; i++)
698c5a17 328 trie->strings_off += sizeof(struct trie_value_entry2_f);
65eb4378
TG
329}
330
331static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) {
332 uint64_t i;
333 struct trie_node_f n = {
334 .prefix_off = htole64(trie->strings_off + node->prefix_off),
335 .children_count = node->children_count,
336 .values_count = htole64(node->values_count),
337 };
338 struct trie_child_entry_f *children = NULL;
339 int64_t node_off;
340
341 if (node->children_count) {
342 children = new0(struct trie_child_entry_f, node->children_count);
343 if (!children)
344 return -ENOMEM;
345 }
346
347 /* post-order recursion */
348 for (i = 0; i < node->children_count; i++) {
349 int64_t child_off;
350
351 child_off = trie_store_nodes(trie, node->children[i].child);
352 if (child_off < 0) {
353 free(children);
354 return child_off;
355 }
356 children[i].c = node->children[i].c;
357 children[i].child_off = htole64(child_off);
358 }
359
360 /* write node */
361 node_off = ftello(trie->f);
362 fwrite(&n, sizeof(struct trie_node_f), 1, trie->f);
363 trie->nodes_count++;
364
365 /* append children array */
366 if (node->children_count) {
367 fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f);
368 trie->children_count += node->children_count;
369 free(children);
370 }
371
372 /* append values array */
373 for (i = 0; i < node->values_count; i++) {
698c5a17 374 struct trie_value_entry2_f v = {
65eb4378
TG
375 .key_off = htole64(trie->strings_off + node->values[i].key_off),
376 .value_off = htole64(trie->strings_off + node->values[i].value_off),
698c5a17
DH
377 .filename_off = htole64(trie->strings_off + node->values[i].filename_off),
378 .line_number = htole64(node->values[i].line_number),
65eb4378
TG
379 };
380
698c5a17 381 fwrite(&v, sizeof(struct trie_value_entry2_f), 1, trie->f);
65eb4378
TG
382 trie->values_count++;
383 }
384
385 return node_off;
386}
387
388static int trie_store(struct trie *trie, const char *filename) {
389 struct trie_f t = {
390 .trie = trie,
391 };
392 _cleanup_free_ char *filename_tmp = NULL;
393 int64_t pos;
394 int64_t root_off;
395 int64_t size;
396 struct trie_header_f h = {
397 .signature = HWDB_SIG,
398 .tool_version = htole64(atoi(VERSION)),
399 .header_size = htole64(sizeof(struct trie_header_f)),
400 .node_size = htole64(sizeof(struct trie_node_f)),
401 .child_entry_size = htole64(sizeof(struct trie_child_entry_f)),
698c5a17 402 .value_entry_size = htole64(sizeof(struct trie_value_entry2_f)),
65eb4378
TG
403 };
404 int err;
405
406 /* calculate size of header, nodes, children entries, value entries */
407 t.strings_off = sizeof(struct trie_header_f);
408 trie_store_nodes_size(&t, trie->root);
409
410 err = fopen_temporary(filename , &t.f, &filename_tmp);
411 if (err < 0)
412 return err;
413 fchmod(fileno(t.f), 0444);
414
415 /* write nodes */
416 err = fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET);
417 if (err < 0) {
418 fclose(t.f);
419 unlink_noerrno(filename_tmp);
420 return -errno;
421 }
422 root_off = trie_store_nodes(&t, trie->root);
423 h.nodes_root_off = htole64(root_off);
424 pos = ftello(t.f);
425 h.nodes_len = htole64(pos - sizeof(struct trie_header_f));
426
427 /* write string buffer */
428 fwrite(trie->strings->buf, trie->strings->len, 1, t.f);
429 h.strings_len = htole64(trie->strings->len);
430
431 /* write header */
432 size = ftello(t.f);
433 h.file_size = htole64(size);
434 err = fseeko(t.f, 0, SEEK_SET);
435 if (err < 0) {
436 fclose(t.f);
437 unlink_noerrno(filename_tmp);
438 return -errno;
439 }
440 fwrite(&h, sizeof(struct trie_header_f), 1, t.f);
441 err = ferror(t.f);
442 if (err)
443 err = -errno;
444 fclose(t.f);
445 if (err < 0 || rename(filename_tmp, filename) < 0) {
446 unlink_noerrno(filename_tmp);
447 return err < 0 ? err : -errno;
448 }
449
450 log_debug("=== trie on-disk ===");
1fa2f38f 451 log_debug("size: %8"PRIi64" bytes", size);
65eb4378
TG
452 log_debug("header: %8zu bytes", sizeof(struct trie_header_f));
453 log_debug("nodes: %8"PRIu64" bytes (%8"PRIu64")",
454 t.nodes_count * sizeof(struct trie_node_f), t.nodes_count);
455 log_debug("child pointers: %8"PRIu64" bytes (%8"PRIu64")",
456 t.children_count * sizeof(struct trie_child_entry_f), t.children_count);
457 log_debug("value pointers: %8"PRIu64" bytes (%8"PRIu64")",
698c5a17 458 t.values_count * sizeof(struct trie_value_entry2_f), t.values_count);
65eb4378
TG
459 log_debug("string store: %8zu bytes", trie->strings->len);
460 log_debug("strings start: %8"PRIu64, t.strings_off);
461
462 return 0;
463}
464
698c5a17
DH
465static int insert_data(struct trie *trie, char **match_list, char *line,
466 const char *filename, size_t line_number) {
65eb4378
TG
467 char *value, **entry;
468
469 value = strchr(line, '=');
470 if (!value) {
471 log_error("Error, key/value pair expected but got '%s' in '%s':", line, filename);
472 return -EINVAL;
473 }
474
475 value[0] = '\0';
476 value++;
477
478 /* libudev requires properties to start with a space */
479 while (isblank(line[0]) && isblank(line[1]))
480 line++;
481
482 if (line[0] == '\0' || value[0] == '\0') {
483 log_error("Error, empty key or value '%s' in '%s':", line, filename);
484 return -EINVAL;
485 }
486
487 STRV_FOREACH(entry, match_list)
698c5a17 488 trie_insert(trie, trie->root, *entry, line, value, filename, line_number);
65eb4378
TG
489
490 return 0;
491}
492
493static int import_file(struct trie *trie, const char *filename) {
494 enum {
495 HW_NONE,
496 HW_MATCH,
497 HW_DATA,
498 } state = HW_NONE;
499 _cleanup_fclose_ FILE *f = NULL;
500 char line[LINE_MAX];
501 _cleanup_strv_free_ char **match_list = NULL;
698c5a17 502 size_t line_number = 0;
65eb4378
TG
503 char *match = NULL;
504 int r;
505
506 f = fopen(filename, "re");
507 if (!f)
508 return -errno;
509
510 while (fgets(line, sizeof(line), f)) {
511 size_t len;
512 char *pos;
513
698c5a17
DH
514 ++line_number;
515
65eb4378
TG
516 /* comment line */
517 if (line[0] == '#')
518 continue;
519
520 /* strip trailing comment */
521 pos = strchr(line, '#');
522 if (pos)
523 pos[0] = '\0';
524
525 /* strip trailing whitespace */
526 len = strlen(line);
527 while (len > 0 && isspace(line[len-1]))
528 len--;
529 line[len] = '\0';
530
531 switch (state) {
532 case HW_NONE:
533 if (len == 0)
534 break;
535
536 if (line[0] == ' ') {
537 log_error("Error, MATCH expected but got '%s' in '%s':", line, filename);
538 break;
539 }
540
541 /* start of record, first match */
542 state = HW_MATCH;
543
544 match = strdup(line);
545 if (!match)
546 return -ENOMEM;
547
548 r = strv_consume(&match_list, match);
549 if (r < 0)
550 return r;
551
552 break;
553
554 case HW_MATCH:
555 if (len == 0) {
556 log_error("Error, DATA expected but got empty line in '%s':", filename);
557 state = HW_NONE;
558 strv_clear(match_list);
559 break;
560 }
561
562 /* another match */
563 if (line[0] != ' ') {
564 match = strdup(line);
565 if (!match)
566 return -ENOMEM;
567
568 r = strv_consume(&match_list, match);
569 if (r < 0)
570 return r;
571
572 break;
573 }
574
575 /* first data */
576 state = HW_DATA;
698c5a17 577 insert_data(trie, match_list, line, filename, line_number);
65eb4378
TG
578 break;
579
580 case HW_DATA:
581 /* end of record */
582 if (len == 0) {
583 state = HW_NONE;
584 strv_clear(match_list);
585 break;
586 }
587
588 if (line[0] != ' ') {
589 log_error("Error, DATA expected but got '%s' in '%s':", line, filename);
590 state = HW_NONE;
591 strv_clear(match_list);
592 break;
593 }
594
698c5a17 595 insert_data(trie, match_list, line, filename, line_number);
65eb4378
TG
596 break;
597 };
598 }
599
600 return 0;
601}
602
caa8dab2 603static int hwdb_query(int argc, char *argv[], void *userdata) {
4afd3348 604 _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
65eb4378
TG
605 const char *key, *value;
606 const char *modalias;
607 int r;
608
caa8dab2
TG
609 assert(argc >= 2);
610 assert(argv);
65eb4378 611
caa8dab2 612 modalias = argv[1];
65eb4378
TG
613
614 r = sd_hwdb_new(&hwdb);
615 if (r < 0)
616 return r;
617
618 SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value)
619 printf("%s=%s\n", key, value);
620
621 return 0;
622}
623
caa8dab2 624static int hwdb_update(int argc, char *argv[], void *userdata) {
65eb4378
TG
625 _cleanup_free_ char *hwdb_bin = NULL;
626 _cleanup_(trie_freep) struct trie *trie = NULL;
627 char **files, **f;
628 int r;
629
630 trie = new0(struct trie, 1);
631 if (!trie)
632 return -ENOMEM;
633
634 /* string store */
635 trie->strings = strbuf_new();
636 if (!trie->strings)
637 return -ENOMEM;
638
639 /* index */
640 trie->root = new0(struct trie_node, 1);
641 if (!trie->root)
642 return -ENOMEM;
643
644 trie->nodes_count++;
645
646 r = conf_files_list_strv(&files, ".hwdb", arg_root, conf_file_dirs);
647 if (r < 0)
648 return log_error_errno(r, "failed to enumerate hwdb files: %m");
649
650 STRV_FOREACH(f, files) {
651 log_debug("reading file '%s'", *f);
652 import_file(trie, *f);
653 }
654 strv_free(files);
655
656 strbuf_complete(trie->strings);
657
658 log_debug("=== trie in-memory ===");
659 log_debug("nodes: %8zu bytes (%8zu)",
660 trie->nodes_count * sizeof(struct trie_node), trie->nodes_count);
661 log_debug("children arrays: %8zu bytes (%8zu)",
662 trie->children_count * sizeof(struct trie_child_entry), trie->children_count);
663 log_debug("values arrays: %8zu bytes (%8zu)",
664 trie->values_count * sizeof(struct trie_value_entry), trie->values_count);
665 log_debug("strings: %8zu bytes",
666 trie->strings->len);
667 log_debug("strings incoming: %8zu bytes (%8zu)",
668 trie->strings->in_len, trie->strings->in_count);
669 log_debug("strings dedup'ed: %8zu bytes (%8zu)",
670 trie->strings->dedup_len, trie->strings->dedup_count);
671
672 hwdb_bin = strjoin(arg_root, "/", arg_hwdb_bin_dir, "/hwdb.bin", NULL);
673 if (!hwdb_bin)
674 return -ENOMEM;
675
ea683512 676 mkdir_parents_label(hwdb_bin, 0755);
65eb4378
TG
677 r = trie_store(trie, hwdb_bin);
678 if (r < 0)
679 return log_error_errno(r, "Failure writing database %s: %m", hwdb_bin);
680
ea683512 681 return label_fix(hwdb_bin, false, false);
65eb4378
TG
682}
683
684static void help(void) {
685 printf("Usage: %s OPTIONS COMMAND\n\n"
424d110a
LP
686 "Update or query the hardware database.\n\n"
687 " -h --help Show this help\n"
688 " --version Show package version\n"
689 " --usr Generate in " UDEVLIBEXECDIR " instead of /etc/udev\n"
690 " -r --root=PATH Alternative root path in the filesystem\n\n"
65eb4378 691 "Commands:\n"
424d110a
LP
692 " update Update the hwdb database\n"
693 " query MODALIAS Query database and print result\n",
65eb4378
TG
694 program_invocation_short_name);
695}
696
697static int parse_argv(int argc, char *argv[]) {
698 enum {
424d110a
LP
699 ARG_VERSION = 0x100,
700 ARG_USR,
65eb4378
TG
701 };
702
703 static const struct option options[] = {
424d110a
LP
704 { "help", no_argument, NULL, 'h' },
705 { "version", no_argument, NULL, ARG_VERSION },
706 { "usr", no_argument, NULL, ARG_USR },
707 { "root", required_argument, NULL, 'r' },
65eb4378
TG
708 {}
709 };
710
711 int c;
712
713 assert(argc >= 0);
714 assert(argv);
715
716 while ((c = getopt_long(argc, argv, "ut:r:h", options, NULL)) >= 0) {
717 switch(c) {
424d110a
LP
718
719 case 'h':
720 help();
721 return 0;
722
723 case ARG_VERSION:
3f6fd1ba 724 return version();
424d110a 725
65eb4378
TG
726 case ARG_USR:
727 arg_hwdb_bin_dir = UDEVLIBEXECDIR;
728 break;
424d110a 729
65eb4378
TG
730 case 'r':
731 arg_root = optarg;
732 break;
424d110a 733
65eb4378
TG
734 case '?':
735 return -EINVAL;
424d110a 736
65eb4378
TG
737 default:
738 assert_not_reached("Unknown option");
739 }
740 }
741
742 return 1;
743}
744
745static int hwdb_main(int argc, char *argv[]) {
caa8dab2
TG
746 const Verb verbs[] = {
747 { "update", 1, 1, 0, hwdb_update },
424d110a 748 { "query", 2, 2, 0, hwdb_query },
caa8dab2 749 {},
65eb4378
TG
750 };
751
caa8dab2 752 return dispatch_verb(argc, argv, verbs, NULL);
65eb4378
TG
753}
754
755int main (int argc, char *argv[]) {
756 int r;
757
758 log_parse_environment();
759 log_open();
760
761 r = parse_argv(argc, argv);
762 if (r <= 0)
763 goto finish;
764
ea683512
EV
765 mac_selinux_init();
766
65eb4378
TG
767 r = hwdb_main(argc, argv);
768
769finish:
770 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
771}