chiark / gitweb /
0746698c46177d82256f4faa2d4d5dd52a7dcf5c
[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         for (;;) {
281                 int option;
282
283                 option = getopt_long(argc, argv, "s", options, NULL);
284                 if (option == -1)
285                         break;
286
287                 switch (option) {
288                 case 's':
289                         subsys = optarg;
290                         break;
291                 }
292         }
293
294         trie.dev = dev;
295         trie.test = test;
296         if (hwdb_lookup(dev, subsys) < 0)
297                 return EXIT_FAILURE;
298         return EXIT_SUCCESS;
299 }
300
301 /* called at udev startup and reload */
302 static int builtin_hwdb_init(struct udev *udev)
303 {
304         struct stat st;
305         const char sig[] = HWDB_SIG;
306
307         trie.f = fopen(SYSCONFDIR "/udev/hwdb.bin", "re");
308         if (!trie.f)
309                 return -errno;
310
311         if (fstat(fileno(trie.f), &st) < 0 || (size_t)st.st_size < offsetof(struct trie_header_f, strings_len) + 8) {
312                 log_error("Error reading '%s'.", SYSCONFDIR "/udev/hwdb.bin: %m");
313                 fclose(trie.f);
314                 zero(trie);
315                 return -EINVAL;
316         }
317
318         trie.map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fileno(trie.f), 0);
319         if (trie.map == MAP_FAILED) {
320                 log_error("Error mapping '%s'.", SYSCONFDIR "/udev/hwdb.bin: %m");
321                 fclose(trie.f);
322                 return -EINVAL;
323         }
324         trie.file_time_usec = ts_usec(&st.st_mtim);
325         trie.map_size = st.st_size;
326
327         if (memcmp(trie.map, sig, sizeof(trie.head->signature)) != 0 || (size_t)st.st_size != le64toh(trie.head->file_size)) {
328                 log_error("Unable to recognize the format of '%s'.", SYSCONFDIR "/udev/hwdb.bin");
329                 log_error("Please try 'udevadm hwdb --update' to re-create it.");
330                 munmap((void *)trie.map, st.st_size);
331                 fclose(trie.f);
332                 zero(trie);
333                 return EINVAL;
334         }
335
336         log_debug("=== trie on-disk ===\n");
337         log_debug("tool version:          %llu", (unsigned long long)le64toh(trie.head->tool_version));
338         log_debug("file size:        %8zi bytes\n", st.st_size);
339         log_debug("header size       %8zu bytes\n", (size_t)le64toh(trie.head->header_size));
340         log_debug("strings           %8zu bytes\n", (size_t)le64toh(trie.head->strings_len));
341         log_debug("nodes             %8zu bytes\n", (size_t)le64toh(trie.head->nodes_len));
342         return 0;
343 }
344
345 /* called on udev shutdown and reload request */
346 static void builtin_hwdb_exit(struct udev *udev)
347 {
348         if (!trie.f)
349                 return;
350         munmap((void *)trie.map, trie.map_size);
351         fclose(trie.f);
352         zero(trie);
353 }
354
355 /* called every couple of seconds during event activity; 'true' if config has changed */
356 static bool builtin_hwdb_validate(struct udev *udev)
357 {
358         struct stat st;
359
360         if (!trie.f)
361                 return true;
362         if (fstat(fileno(trie.f), &st) < 0)
363                 return true;
364         if (trie.file_time_usec != ts_usec(&st.st_mtim))
365                 return true;
366         return false;
367 }
368
369 const struct udev_builtin udev_builtin_hwdb = {
370         .name = "hwdb",
371         .cmd = builtin_hwdb,
372         .init = builtin_hwdb_init,
373         .exit = builtin_hwdb_exit,
374         .validate = builtin_hwdb_validate,
375         .help = "hardware database",
376 };