chiark / gitweb /
1458795e993bb151d69b0164421aafc725f71081
[elogind.git] / src / udev / udev-builtin-hwdb.c
1 /***
2   This file is part of systemd.
3
4   Copyright 2012 Kay Sievers <kay.sievers@vrfy.org>
5   Copyright 2008 Alan Jenkins <alan.christopher.jenkins@googlemail.com>
6
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.
11
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.
16
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/>.
19 ***/
20
21 #include <stdio.h>
22 #include <errno.h>
23 #include <string.h>
24 #include <inttypes.h>
25 #include <ctype.h>
26 #include <stdlib.h>
27 #include <fnmatch.h>
28 #include <getopt.h>
29 #include <sys/mman.h>
30
31 #include "udev.h"
32 #include "udev-hwdb.h"
33
34 struct linebuf {
35         char bytes[LINE_MAX];
36         size_t size;
37         size_t len;
38 };
39
40 static void linebuf_init(struct linebuf *buf) {
41         buf->size = 0;
42         buf->len = 0;
43 }
44
45 static const char *linebuf_get(struct linebuf *buf) {
46         if (buf->len + 1 >= sizeof(buf->bytes))
47                 return NULL;
48         buf->bytes[buf->len] = '\0';
49         return buf->bytes;
50 }
51
52 static bool linebuf_add(struct linebuf *buf, const char *s, size_t len) {
53         if (buf->len + len >= sizeof(buf->bytes))
54                 return false;
55         memcpy(buf->bytes + buf->len, s, len);
56         buf->len += len;
57         return true;
58 }
59
60 static bool linebuf_add_char(struct linebuf *buf, char c)
61 {
62         if (buf->len + 1 >= sizeof(buf->bytes))
63                 return false;
64         buf->bytes[buf->len++] = c;
65         return true;
66 }
67
68 static void linebuf_rem(struct linebuf *buf, size_t count) {
69         assert(buf->len >= count);
70         buf->len -= count;
71 }
72
73 static void linebuf_rem_char(struct linebuf *buf) {
74         linebuf_rem(buf, 1);
75 }
76
77 struct trie_f {
78         struct udev_device *dev;
79         bool test;
80         FILE *f;
81         uint64_t file_time_usec;
82         union {
83                 struct trie_header_f *head;
84                 const char *map;
85         };
86         size_t map_size;
87 };
88
89 static const struct trie_child_entry_f *trie_node_children(struct trie_f *trie, const struct trie_node_f *node) {
90         return (const struct trie_child_entry_f *)((const char *)node + le64toh(trie->head->node_size));
91 }
92
93 static const struct trie_value_entry_f *trie_node_values(struct trie_f *trie, const struct trie_node_f *node) {
94         const char *base = (const char *)node;
95
96         base += le64toh(trie->head->node_size);
97         base += node->children_count * le64toh(trie->head->child_entry_size);
98         return (const struct trie_value_entry_f *)base;
99 }
100
101 static const struct trie_node_f *trie_node_from_off(struct trie_f *trie, le64_t off) {
102         return (const struct trie_node_f *)(trie->map + le64toh(off));
103 }
104
105 static const char *trie_string(struct trie_f *trie, le64_t off) {
106         return trie->map + le64toh(off);
107 }
108
109 static int trie_children_cmp_f(const void *v1, const void *v2) {
110         const struct trie_child_entry_f *n1 = v1;
111         const struct trie_child_entry_f *n2 = v2;
112
113         return n1->c - n2->c;
114 }
115
116 static const struct trie_node_f *node_lookup_f(struct trie_f *trie, const struct trie_node_f *node, uint8_t c) {
117         struct trie_child_entry_f *child;
118         struct trie_child_entry_f search;
119
120         search.c = c;
121         child = bsearch(&search, trie_node_children(trie, node), node->children_count,
122                         le64toh(trie->head->child_entry_size), trie_children_cmp_f);
123         if (child)
124                 return trie_node_from_off(trie, child->child_off);
125         return NULL;
126 }
127
128 static void trie_fnmatch_f(struct trie_f *trie, const struct trie_node_f *node, size_t p,
129                            struct linebuf *buf, const char *search,
130                            void (*cb)(struct trie_f *trie, const char *key, const char *value)) {
131         size_t len;
132         size_t i;
133         const char *prefix;
134
135         prefix = trie_string(trie, node->prefix_off);
136         len = strlen(prefix + p);
137         linebuf_add(buf, prefix + p, len);
138
139         for (i = 0; i < node->children_count; i++) {
140                 const struct trie_child_entry_f *child = &trie_node_children(trie, node)[i];
141
142                 linebuf_add_char(buf, child->c);
143                 trie_fnmatch_f(trie, trie_node_from_off(trie, child->child_off), 0, buf, search, cb);
144                 linebuf_rem_char(buf);
145         }
146
147         if (node->values_count && fnmatch(linebuf_get(buf), search, 0) == 0)
148                 for (i = 0; i < node->values_count; i++)
149                         cb(trie, trie_string(trie, trie_node_values(trie, node)[i].key_off),
150                            trie_string(trie, trie_node_values(trie, node)[i].value_off));
151
152         linebuf_rem(buf, len);
153 }
154
155 static void trie_search_f(struct trie_f *trie, const char *search,
156                           void (*cb)(struct trie_f *trie, const char *key, const char *value)) {
157         struct linebuf buf;
158         const struct trie_node_f *node;
159         size_t i = 0;
160
161         linebuf_init(&buf);
162
163         node = trie_node_from_off(trie, trie->head->nodes_root_off);
164         while (node) {
165                 const struct trie_node_f *child;
166                 size_t p = 0;
167
168                 if (node->prefix_off) {
169                         uint8_t c;
170
171                         for (; (c = trie_string(trie, node->prefix_off)[p]); p++) {
172                                 if (c == '*' || c == '?' || c == '[') {
173                                         trie_fnmatch_f(trie, node, p, &buf, search + i + p, cb);
174                                         return;
175                                 }
176                                 if (c != search[i + p])
177                                         return;
178                         }
179                         i += p;
180                 }
181
182                 child = node_lookup_f(trie, node, '*');
183                 if (child) {
184                         linebuf_add_char(&buf, '*');
185                         trie_fnmatch_f(trie, child, 0, &buf, search + i, cb);
186                         linebuf_rem_char(&buf);
187                 }
188
189                 child = node_lookup_f(trie, node, '?');
190                 if (child) {
191                         linebuf_add_char(&buf, '?');
192                         trie_fnmatch_f(trie, child, 0, &buf, search + i, cb);
193                         linebuf_rem_char(&buf);
194                 }
195
196                 child = node_lookup_f(trie, node, '[');
197                 if (child) {
198                         linebuf_add_char(&buf, '[');
199                         trie_fnmatch_f(trie, child, 0, &buf, search + i, cb);
200                         linebuf_rem_char(&buf);
201                 }
202
203                 if (search[i] == '\0') {
204                         size_t n;
205
206                         for (n = 0; n < node->values_count; n++)
207                                 cb(trie, trie_string(trie, trie_node_values(trie, node)[n].key_off),
208                                    trie_string(trie, trie_node_values(trie, node)[n].value_off));
209                         return;
210                 }
211
212                 child = node_lookup_f(trie, node, search[i]);
213                 node = child;
214                 i++;
215         }
216 }
217
218 static void value_cb(struct trie_f *trie, const char *key, const char *value) {
219         /* TODO: add sub-matches (+) against DMI data */
220         if (key[0] == ' ')
221                 udev_builtin_add_property(trie->dev, trie->test, key + 1, value);
222 }
223
224 static struct trie_f trie;
225
226 static int hwdb_lookup(struct udev_device *dev, const char *subsys) {
227         struct udev_device *d;
228         const char *modalias;
229         char str[UTIL_NAME_SIZE];
230         int rc = EXIT_SUCCESS;
231
232         /* search the first parent device with a modalias */
233         for (d = dev; d; d = udev_device_get_parent(d)) {
234                 const char *dsubsys = udev_device_get_subsystem(d);
235
236                 /* look only at devices of a specific subsystem */
237                 if (subsys && dsubsys && !streq(dsubsys, subsys))
238                         continue;
239
240                 modalias = udev_device_get_property_value(d, "MODALIAS");
241                 if (modalias)
242                         break;
243
244                 /* the usb_device does not have modalias, compose one */
245                 if (dsubsys && streq(dsubsys, "usb")) {
246                         const char *v, *p;
247                         int vn, pn;
248
249                         v = udev_device_get_sysattr_value(d, "idVendor");
250                         if (!v)
251                                 continue;
252                         p = udev_device_get_sysattr_value(d, "idProduct");
253                         if (!p)
254                                 continue;
255                         vn = strtol(v, NULL, 16);
256                         if (vn <= 0)
257                                 continue;
258                         pn = strtol(p, NULL, 16);
259                         if (pn <= 0)
260                                 continue;
261                         snprintf(str, sizeof(str), "usb:v%04Xp%04X*", vn, pn);
262                         modalias = str;
263                         break;
264                 }
265         }
266         if (!modalias)
267                 return EXIT_FAILURE;
268
269         trie_search_f(&trie, modalias, value_cb);
270         return rc;
271 }
272
273 static int builtin_hwdb(struct udev_device *dev, int argc, char *argv[], bool test) {
274         static const struct option options[] = {
275                 { "subsystem", required_argument, NULL, 's' },
276                 {}
277         };
278         const char *subsys = NULL;
279
280         if (!trie.f)
281                 return EXIT_SUCCESS;
282
283         for (;;) {
284                 int option;
285
286                 option = getopt_long(argc, argv, "s", options, NULL);
287                 if (option == -1)
288                         break;
289
290                 switch (option) {
291                 case 's':
292                         subsys = optarg;
293                         break;
294                 }
295         }
296
297         trie.dev = dev;
298         trie.test = test;
299         if (hwdb_lookup(dev, subsys) < 0)
300                 return EXIT_FAILURE;
301         return EXIT_SUCCESS;
302 }
303
304 /* called at udev startup and reload */
305 static int builtin_hwdb_init(struct udev *udev)
306 {
307         struct stat st;
308         const char sig[] = HWDB_SIG;
309
310         trie.f = fopen(SYSCONFDIR "/udev/hwdb.bin", "re");
311         if (!trie.f)
312                 return -errno;
313
314         if (fstat(fileno(trie.f), &st) < 0 || (size_t)st.st_size < offsetof(struct trie_header_f, strings_len) + 8) {
315                 log_error("Error reading '%s'.", SYSCONFDIR "/udev/hwdb.bin: %m");
316                 fclose(trie.f);
317                 zero(trie);
318                 return -EINVAL;
319         }
320
321         trie.map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fileno(trie.f), 0);
322         if (trie.map == MAP_FAILED) {
323                 log_error("Error mapping '%s'.", SYSCONFDIR "/udev/hwdb.bin: %m");
324                 fclose(trie.f);
325                 return -EINVAL;
326         }
327         trie.file_time_usec = ts_usec(&st.st_mtim);
328         trie.map_size = st.st_size;
329
330         if (memcmp(trie.map, sig, sizeof(trie.head->signature)) != 0 || (size_t)st.st_size != le64toh(trie.head->file_size)) {
331                 log_error("Unable to recognize the format of '%s'.", SYSCONFDIR "/udev/hwdb.bin");
332                 log_error("Please try 'udevadm hwdb --update' to re-create it.");
333                 munmap((void *)trie.map, st.st_size);
334                 fclose(trie.f);
335                 zero(trie);
336                 return EINVAL;
337         }
338
339         log_debug("=== trie on-disk ===\n");
340         log_debug("tool version:          %llu", (unsigned long long)le64toh(trie.head->tool_version));
341         log_debug("file size:        %8zi bytes\n", st.st_size);
342         log_debug("header size       %8zu bytes\n", (size_t)le64toh(trie.head->header_size));
343         log_debug("strings           %8zu bytes\n", (size_t)le64toh(trie.head->strings_len));
344         log_debug("nodes             %8zu bytes\n", (size_t)le64toh(trie.head->nodes_len));
345         return 0;
346 }
347
348 /* called on udev shutdown and reload request */
349 static void builtin_hwdb_exit(struct udev *udev)
350 {
351         if (!trie.f)
352                 return;
353         munmap((void *)trie.map, trie.map_size);
354         fclose(trie.f);
355         zero(trie);
356 }
357
358 /* called every couple of seconds during event activity; 'true' if config has changed */
359 static bool builtin_hwdb_validate(struct udev *udev)
360 {
361         struct stat st;
362
363         if (!trie.f)
364                 return true;
365         if (fstat(fileno(trie.f), &st) < 0)
366                 return true;
367         if (trie.file_time_usec != ts_usec(&st.st_mtim))
368                 return true;
369         return false;
370 }
371
372 const struct udev_builtin udev_builtin_hwdb = {
373         .name = "hwdb",
374         .cmd = builtin_hwdb,
375         .init = builtin_hwdb_init,
376         .exit = builtin_hwdb_exit,
377         .validate = builtin_hwdb_validate,
378         .help = "hardware database",
379 };