chiark / gitweb /
udev: add hardware database support
[elogind.git] / src / udev / udevadm-hwdb.c
1 /***
2   This file is part of systemd.
3
4   Copyright 2012 Kay Sievers <kay.sievers@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
25 #include "util.h"
26 #include "strbuf.h"
27 #include "conf-files.h"
28
29 #include "udev.h"
30 #include "udev-hwdb.h"
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
37 static const char * const conf_file_dirs[] = {
38         SYSCONFDIR "/udev/hwdb.d",
39         UDEVLIBEXECDIR "/hwdb.d",
40         NULL
41 };
42
43 /* in-memory trie objects */
44 struct 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
53 struct 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 */
67 struct trie_child_entry {
68         uint8_t c;
69         struct trie_node *child;
70 };
71
72 /* value array item with key/value pairs */
73 struct trie_value_entry {
74         size_t key_off;
75         size_t value_off;
76 };
77
78 static 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
85 static 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++;
102 out:
103         return err;
104 }
105
106 static 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
117 static 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
127 static 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
136 static int trie_node_add_value(struct trie *trie, struct trie_node *node,
137                           const char *key, const char *value) {
138         size_t k, v;
139         struct trie_value_entry *val;
140         struct trie_value_entry search;
141
142         k = strbuf_add_string(trie->strings, key, strlen(key));
143         v = strbuf_add_string(trie->strings, value, strlen(value));
144
145         /* replace existing earlier key with new value */
146         search.value_off = k;
147         val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
148         if (val) {
149                 val->value_off = v;
150                 return 0;
151         }
152
153         /* extend array, add new entry, sort for bisection */
154         val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry));
155         if (!val)
156                 return -ENOMEM;
157         trie->values_count++;
158         node->values = val;
159         node->values[node->values_count].key_off = k;
160         node->values[node->values_count].value_off = v;
161         node->values_count++;
162         qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie);
163         return 0;
164 }
165
166 static int trie_insert(struct trie *trie, struct trie_node *node, const char *search,
167                        const char *key, const char *value) {
168         size_t i = 0;
169         int err = 0;
170
171         for (;;) {
172                 size_t p;
173                 uint8_t c;
174                 struct trie_node *child;
175
176                 for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) {
177                         char *s;
178                         ssize_t off;
179
180                         if (c == search[i + p])
181                                 continue;
182
183                         /* split node */
184                         child = calloc(sizeof(struct trie_node), 1);
185                         if (!child) {
186                                 err = -ENOMEM;
187                                 goto out;
188                         }
189
190                         /* move values from parent to child */
191                         child->prefix_off = node->prefix_off + p+1;
192                         child->children = node->children;
193                         child->children_count = node->children_count;
194                         child->values = node->values;
195                         child->values_count = node->values_count;
196
197                         /* update parent; use strdup() because the source gets realloc()d */
198                         s = strndup(trie->strings->buf + node->prefix_off, p);
199                         if (!s) {
200                                 err = -ENOMEM;
201                                 goto out;
202                         }
203                         off = strbuf_add_string(trie->strings, s, p);
204                         free(s);
205                         if (off < 0) {
206                                 err = off;
207                                 goto out;
208                         }
209                         node->prefix_off = off;
210                         node->children = NULL;
211                         node->children_count = 0;
212                         node->values = NULL;
213                         node->values_count = 0;
214                         err = node_add_child(trie, node, child, c);
215                         if (err)
216                                 goto out;
217                         break;
218                 }
219                 i += p;
220
221                 c = search[i];
222                 if (c == '\0')
223                         return trie_node_add_value(trie, node, key, value);
224
225                 child = node_lookup(node, c);
226                 if (!child) {
227                         ssize_t off;
228
229                         /* new child */
230                         child = calloc(sizeof(struct trie_node), 1);
231                         if (!child) {
232                                 err = -ENOMEM;
233                                 goto out;
234                         }
235                         off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1));
236                         if (off < 0) {
237                                 err = off;
238                                 goto out;
239                         }
240                         child->prefix_off = off;
241                         err = node_add_child(trie, node, child, c);
242                         if (err)
243                                 goto out;
244                         return trie_node_add_value(trie, child, key, value);
245                 }
246
247                 node = child;
248                 i++;
249         }
250 out:
251         return err;
252 }
253
254 struct trie_f {
255         FILE *f;
256         struct trie *trie;
257         uint64_t strings_off;
258
259         uint64_t nodes_count;
260         uint64_t children_count;
261         uint64_t values_count;
262 };
263
264 /* calculate the storage space for the nodes, children arrays, value arrays */
265 static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) {
266         uint64_t i;
267
268         for (i = 0; i < node->children_count; i++)
269                 trie_store_nodes_size(trie, node->children[i].child);
270
271         trie->strings_off += sizeof(struct trie_node_f);
272         for (i = 0; i < node->children_count; i++)
273                 trie->strings_off += sizeof(struct trie_child_entry_f);
274         for (i = 0; i < node->values_count; i++)
275                 trie->strings_off += sizeof(struct trie_value_entry_f);
276 }
277
278 static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) {
279         uint64_t i;
280         struct trie_node_f n = {
281                 .prefix_off = htole64(trie->strings_off + node->prefix_off),
282                 .children_count = node->children_count,
283                 .values_count = htole64(node->values_count),
284         };
285         struct trie_child_entry_f *children;
286         int64_t node_off;
287
288         if (node->children_count) {
289                 children = new0(struct trie_child_entry_f, node->children_count);
290                 if (!children)
291                         return -ENOMEM;
292         }
293
294         /* post-order recursion */
295         for (i = 0; i < node->children_count; i++) {
296                 int64_t child_off;
297
298                 child_off = trie_store_nodes(trie, node->children[i].child);
299                 if (child_off < 0)
300                         return child_off;
301                 children[i].c = node->children[i].c;
302                 children[i].child_off = htole64(child_off);
303         }
304
305         /* write node */
306         node_off = ftello(trie->f);
307         fwrite(&n, sizeof(struct trie_node_f), 1, trie->f);
308         trie->nodes_count++;
309
310         /* append children array */
311         if (node->children_count) {
312                 fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f);
313                 trie->children_count += node->children_count;
314                 free(children);
315         }
316
317         /* append values array */
318         for (i = 0; i < node->values_count; i++) {
319                 struct trie_value_entry_f v = {
320                         .key_off = htole64(trie->strings_off + node->values[i].key_off),
321                         .value_off = htole64(trie->strings_off + node->values[i].value_off),
322                 };
323
324                 fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f);
325                 trie->values_count++;
326         }
327
328         return node_off;
329 }
330
331 static int trie_store(struct trie *trie, const char *filename) {
332         struct trie_f t = {
333                 .trie = trie,
334         };
335         char *filename_tmp;
336         int64_t pos;
337         int64_t root_off;
338         int64_t size;
339         struct trie_header_f h = {
340                 .signature = HWDB_SIG,
341                 .tool_version = htole64(atoi(VERSION)),
342                 .header_size = htole64(sizeof(struct trie_header_f)),
343                 .node_size = htole64(sizeof(struct trie_node_f)),
344                 .child_entry_size = htole64(sizeof(struct trie_child_entry_f)),
345                 .value_entry_size = htole64(sizeof(struct trie_value_entry_f)),
346         };
347         int err;
348
349         /* calculate size of header, nodes, children entries, value entries */
350         t.strings_off = sizeof(struct trie_header_f);
351         trie_store_nodes_size(&t, trie->root);
352
353         err = fopen_temporary(filename , &t.f, &filename_tmp);
354         if (err < 0)
355                 return err;
356         fchmod(fileno(t.f), 0444);
357
358         /* write nodes */
359         fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET);
360         root_off = trie_store_nodes(&t, trie->root);
361         h.nodes_root_off = htole64(root_off);
362         pos = ftello(t.f);
363         h.nodes_len = htole64(pos - sizeof(struct trie_header_f));
364
365         /* write string buffer */
366         fwrite(trie->strings->buf, trie->strings->len, 1, t.f);
367         h.strings_len = htole64(trie->strings->len);
368
369         /* write header */
370         size = ftello(t.f);
371         h.file_size = htole64(size);
372         fseeko(t.f, 0, SEEK_SET);
373         fwrite(&h, sizeof(struct trie_header_f), 1, t.f);
374         err = ferror(t.f);
375         if (err)
376                 err = -errno;
377         fclose(t.f);
378         if (err < 0 || rename(filename_tmp, filename) < 0) {
379                 unlink(filename_tmp);
380                 goto out;
381         }
382
383         log_debug("=== trie on-disk ===\n");
384         log_debug("size:             %8zi bytes\n", size);
385         log_debug("header:           %8zu bytes\n", sizeof(struct trie_header_f));
386         log_debug("nodes:            %8zu bytes (%8zi)\n", t.nodes_count * sizeof(struct trie_node_f), t.nodes_count);
387         log_debug("child pointers:   %8zu bytes (%8zi)\n", t.children_count * sizeof(struct trie_child_entry_f), t.children_count);
388         log_debug("value pointers:   %8zu bytes (%8zi)\n", t.values_count * sizeof(struct trie_value_entry_f), t.values_count);
389         log_debug("string store:     %8zu bytes\n", trie->strings->len);
390         log_debug("strings start:    %8llu\n", (unsigned long long) t.strings_off);
391 out:
392         free(filename_tmp);
393         return err;
394 }
395
396 static int import_file(struct trie *trie, const char *filename) {
397         FILE *f;
398         char line[LINE_MAX];
399         char match[LINE_MAX];
400
401         f = fopen(filename, "re");
402         if (f == NULL)
403                 return -errno;
404
405         match[0] = '\0';
406         while (fgets(line, sizeof(line), f)) {
407                 size_t len;
408
409                 if (line[0] == '#')
410                         continue;
411
412                 /* new line, new record */
413                 if (line[0] == '\n') {
414                         match[0] = '\0';
415                         continue;
416                 }
417
418                 /* remove newline */
419                 len = strlen(line);
420                 if (len < 2)
421                         continue;
422                 line[len-1] = '\0';
423
424                 /* start of new record */
425                 if (match[0] == '\0') {
426                         strcpy(match, line);
427                         continue;
428                 }
429
430                 /* value lines */
431                 if (line[0] == ' ') {
432                         char *value;
433
434                         value = strchr(line, '=');
435                         if (!value)
436                                 continue;
437                         value[0] = '\0';
438                         value++;
439                         trie_insert(trie, trie->root, match, line, value);
440                 }
441         }
442         fclose(f);
443         return 0;
444 }
445
446 static void help(void) {
447         printf("Usage: udevadm hwdb [--create] [--help]\n"
448                "  --update            update the hardware database\n"
449                "  --help\n\n");
450 }
451
452 static int adm_hwdb(struct udev *udev, int argc, char *argv[]) {
453         static const struct option options[] = {
454                 { "update", no_argument, NULL, 'u' },
455                 { "help", no_argument, NULL, 'h' },
456                 {}
457         };
458         bool update = false;
459         struct trie *trie;
460         char **files, **f;
461         int err;
462         int rc = EXIT_SUCCESS;
463
464         for (;;) {
465                 int option;
466
467                 option = getopt_long(argc, argv, "ch", options, NULL);
468                 if (option == -1)
469                         break;
470
471                 switch (option) {
472                 case 'u':
473                         update = true;
474                         break;
475                 case 'h':
476                         help();
477                         return EXIT_SUCCESS;
478                 }
479         }
480
481         if (!update) {
482                 help();
483                 return EXIT_SUCCESS;
484         }
485
486         trie = calloc(sizeof(struct trie), 1);
487         if (!trie) {
488                 rc = EXIT_FAILURE;
489                 goto out;
490         }
491
492         /* string store */
493         trie->strings = strbuf_new();
494         if (!trie->strings) {
495                 rc = EXIT_FAILURE;
496                 goto out;
497         }
498
499         /* index */
500         trie->root = calloc(sizeof(struct trie_node), 1);
501         if (!trie->root) {
502                 rc = EXIT_FAILURE;
503                 goto out;
504         }
505         trie->nodes_count++;
506
507         err = conf_files_list_strv(&files, ".hwdb", (const char **)conf_file_dirs);
508         if (err < 0) {
509                 log_error("failed to enumerate hwdb files: %s\n", strerror(-err));
510                 rc = EXIT_FAILURE;
511                 goto out;
512         }
513         STRV_FOREACH(f, files) {
514                 log_debug("reading file '%s'", *f);
515                 import_file(trie, *f);
516         }
517         strv_free(files);
518
519         strbuf_complete(trie->strings);
520
521         log_debug("=== trie in-memory ===\n");
522         log_debug("nodes:            %8zu bytes (%8zu)\n", trie->nodes_count * sizeof(struct trie_node), trie->nodes_count);
523         log_debug("children arrays:  %8zu bytes (%8zu)\n", trie->children_count * sizeof(struct trie_child_entry), trie->children_count);
524         log_debug("values arrays:    %8zu bytes (%8zu)\n", trie->values_count * sizeof(struct trie_value_entry), trie->values_count);
525         log_debug("strings:          %8zu bytes\n", trie->strings->len);
526         log_debug("strings incoming: %8zu bytes (%8zu)\n", trie->strings->in_len, trie->strings->in_count);
527         log_debug("strings dedup'ed: %8zu bytes (%8zu)\n", trie->strings->dedup_len, trie->strings->dedup_count);
528
529         mkdir_parents(SYSCONFDIR "/udev/hwdb.bin", 0755);
530         err = trie_store(trie, SYSCONFDIR "/udev/hwdb.bin");
531         if (err < 0) {
532                 log_error("Failure writing hardware database '%s': %s", SYSCONFDIR "/udev/hwdb.bin", strerror(-err));
533                 rc = EXIT_FAILURE;
534         }
535
536 out:
537         if (trie->root)
538                 trie_node_cleanup(trie->root);
539         strbuf_cleanup(trie->strings);
540         free(trie);
541         return rc;
542 }
543
544 const struct udevadm_cmd udevadm_hwdb = {
545         .name = "hwdb",
546         .cmd = adm_hwdb,
547         .help = "maintain the hardware database index",
548 };