]>
Commit | Line | Data |
---|---|---|
796b06c2 KS |
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 | ***/ | |
ccba91c7 LP |
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> | |
796b06c2 KS |
27 | #include <fnmatch.h> |
28 | #include <getopt.h> | |
29 | #include <sys/mman.h> | |
ccba91c7 | 30 | |
3cf5266b | 31 | #include "udev.h" |
796b06c2 | 32 | #include "udev-hwdb.h" |
ccba91c7 | 33 | |
796b06c2 KS |
34 | struct linebuf { |
35 | char bytes[LINE_MAX]; | |
36 | size_t size; | |
37 | size_t len; | |
38 | }; | |
ccba91c7 | 39 | |
796b06c2 KS |
40 | static void linebuf_init(struct linebuf *buf) { |
41 | buf->size = 0; | |
42 | buf->len = 0; | |
43 | } | |
ccba91c7 | 44 | |
796b06c2 KS |
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 | } | |
ccba91c7 | 59 | |
796b06c2 KS |
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 | } | |
ccba91c7 | 67 | |
796b06c2 KS |
68 | static void linebuf_rem(struct linebuf *buf, size_t count) { |
69 | assert(buf->len >= count); | |
70 | buf->len -= count; | |
71 | } | |
ccba91c7 | 72 | |
796b06c2 KS |
73 | static void linebuf_rem_char(struct linebuf *buf) { |
74 | linebuf_rem(buf, 1); | |
ccba91c7 LP |
75 | } |
76 | ||
796b06c2 KS |
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 | }; | |
912541b0 | 88 | |
796b06c2 KS |
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 | } | |
912541b0 | 92 | |
796b06c2 KS |
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; | |
912541b0 | 95 | |
796b06c2 KS |
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; | |
ccba91c7 LP |
99 | } |
100 | ||
796b06c2 KS |
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 | } | |
ccba91c7 | 104 | |
796b06c2 KS |
105 | static const char *trie_string(struct trie_f *trie, le64_t off) { |
106 | return trie->map + le64toh(off); | |
ccba91c7 LP |
107 | } |
108 | ||
796b06c2 KS |
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; | |
ccba91c7 | 112 | |
796b06c2 KS |
113 | return n1->c - n2->c; |
114 | } | |
ccba91c7 | 115 | |
796b06c2 KS |
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; | |
ccba91c7 | 119 | |
796b06c2 KS |
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 | } | |
ccba91c7 | 127 | |
796b06c2 KS |
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; | |
ccba91c7 | 134 | |
796b06c2 KS |
135 | prefix = trie_string(trie, node->prefix_off); |
136 | len = strlen(prefix + p); | |
137 | linebuf_add(buf, prefix + p, len); | |
ccba91c7 | 138 | |
796b06c2 KS |
139 | for (i = 0; i < node->children_count; i++) { |
140 | const struct trie_child_entry_f *child = &trie_node_children(trie, node)[i]; | |
ccba91c7 | 141 | |
796b06c2 KS |
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 | } | |
ccba91c7 | 146 | |
796b06c2 KS |
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)); | |
ccba91c7 | 151 | |
796b06c2 KS |
152 | linebuf_rem(buf, len); |
153 | } | |
ccba91c7 | 154 | |
796b06c2 KS |
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; | |
ccba91c7 | 160 | |
796b06c2 | 161 | linebuf_init(&buf); |
ccba91c7 | 162 | |
796b06c2 KS |
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; | |
ccba91c7 | 167 | |
796b06c2 KS |
168 | if (node->prefix_off) { |
169 | uint8_t c; | |
ccba91c7 | 170 | |
796b06c2 KS |
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; | |
912541b0 | 180 | } |
ccba91c7 | 181 | |
796b06c2 KS |
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 | } | |
ccba91c7 | 188 | |
796b06c2 KS |
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 | } | |
ccba91c7 | 195 | |
796b06c2 KS |
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 | } | |
ccba91c7 | 202 | |
796b06c2 KS |
203 | if (search[i] == '\0') { |
204 | size_t n; | |
ccba91c7 | 205 | |
796b06c2 KS |
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; | |
912541b0 | 210 | } |
796b06c2 KS |
211 | |
212 | child = node_lookup_f(trie, node, search[i]); | |
213 | node = child; | |
214 | i++; | |
912541b0 | 215 | } |
796b06c2 | 216 | } |
ccba91c7 | 217 | |
796b06c2 KS |
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 | } | |
ccba91c7 | 223 | |
796b06c2 | 224 | static struct trie_f trie; |
ccba91c7 | 225 | |
796b06c2 KS |
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; | |
ccba91c7 | 231 | |
796b06c2 KS |
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); | |
ccba91c7 | 235 | |
796b06c2 KS |
236 | /* look only at devices of a specific subsystem */ |
237 | if (subsys && dsubsys && !streq(dsubsys, subsys)) | |
238 | continue; | |
ccba91c7 | 239 | |
796b06c2 KS |
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 | } | |
912541b0 | 265 | } |
796b06c2 KS |
266 | if (!modalias) |
267 | return EXIT_FAILURE; | |
268 | ||
269 | trie_search_f(&trie, modalias, value_cb); | |
270 | return rc; | |
ccba91c7 LP |
271 | } |
272 | ||
796b06c2 KS |
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; | |
ccba91c7 | 279 | |
796b06c2 KS |
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 | } | |
912541b0 | 292 | } |
ccba91c7 | 293 | |
796b06c2 KS |
294 | trie.dev = dev; |
295 | trie.test = test; | |
296 | if (hwdb_lookup(dev, subsys) < 0) | |
297 | return EXIT_FAILURE; | |
298 | return EXIT_SUCCESS; | |
299 | } | |
ccba91c7 | 300 | |
796b06c2 KS |
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 | } | |
ccba91c7 | 317 | |
796b06c2 KS |
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 | } | |
ccba91c7 | 335 | |
796b06c2 KS |
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)); | |
912541b0 | 342 | return 0; |
3cf5266b | 343 | } |
ccba91c7 | 344 | |
796b06c2 KS |
345 | /* called on udev shutdown and reload request */ |
346 | static void builtin_hwdb_exit(struct udev *udev) | |
3cf5266b | 347 | { |
796b06c2 KS |
348 | if (!trie.f) |
349 | return; | |
350 | munmap((void *)trie.map, trie.map_size); | |
351 | fclose(trie.f); | |
352 | zero(trie); | |
ccba91c7 | 353 | } |
3cf5266b | 354 | |
796b06c2 KS |
355 | /* called every couple of seconds during event activity; 'true' if config has changed */ |
356 | static bool builtin_hwdb_validate(struct udev *udev) | |
3cf5266b | 357 | { |
796b06c2 | 358 | struct stat st; |
3cf5266b | 359 | |
796b06c2 KS |
360 | if (fstat(fileno(trie.f), &st) < 0) |
361 | return true; | |
362 | if (trie.file_time_usec != ts_usec(&st.st_mtim)) | |
363 | return true; | |
364 | return false; | |
365 | } | |
3cf5266b | 366 | |
796b06c2 KS |
367 | const struct udev_builtin udev_builtin_hwdb = { |
368 | .name = "hwdb", | |
369 | .cmd = builtin_hwdb, | |
370 | .init = builtin_hwdb_init, | |
371 | .exit = builtin_hwdb_exit, | |
372 | .validate = builtin_hwdb_validate, | |
373 | .help = "hardware database", | |
912541b0 | 374 | .run_once = true, |
3cf5266b | 375 | }; |