]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/udev/udev-builtin-hwdb.c
udev: add hardware database support
[thirdparty/systemd.git] / src / udev / udev-builtin-hwdb.c
CommitLineData
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
34struct linebuf {
35 char bytes[LINE_MAX];
36 size_t size;
37 size_t len;
38};
ccba91c7 39
796b06c2
KS
40static void linebuf_init(struct linebuf *buf) {
41 buf->size = 0;
42 buf->len = 0;
43}
ccba91c7 44
796b06c2
KS
45static 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
52static 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
60static 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
68static void linebuf_rem(struct linebuf *buf, size_t count) {
69 assert(buf->len >= count);
70 buf->len -= count;
71}
ccba91c7 72
796b06c2
KS
73static void linebuf_rem_char(struct linebuf *buf) {
74 linebuf_rem(buf, 1);
ccba91c7
LP
75}
76
796b06c2
KS
77struct 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
89static 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
93static 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
101static 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
105static const char *trie_string(struct trie_f *trie, le64_t off) {
106 return trie->map + le64toh(off);
ccba91c7
LP
107}
108
796b06c2
KS
109static 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
116static 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
128static 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
155static 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
218static 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 224static struct trie_f trie;
ccba91c7 225
796b06c2
KS
226static 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
273static 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 */
302static 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 */
346static 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 */
356static 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
367const 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};