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