]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libudev/libudev-hwdb.c
hwdb: use $(localstatedir)/lib/udev/hwdb.bin for the binary database
[thirdparty/systemd.git] / src / libudev / libudev-hwdb.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2012 Kay Sievers <kay@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 "libudev-private.h"
32 #include "libudev-hwdb-def.h"
33
34 /**
35 * SECTION:libudev-hwdb
36 * @short_description: retrieve properties from the hardware database
37 *
38 * Libudev hardware database interface.
39 */
40
41 /**
42 * udev_hwdb:
43 *
44 * Opaque object representing the hardware database.
45 */
46 struct udev_hwdb {
47 struct udev *udev;
48 int refcount;
49
50 FILE *f;
51 struct stat st;
52 union {
53 struct trie_header_f *head;
54 const char *map;
55 };
56
57 struct udev_list properties_list;
58 };
59
60 struct linebuf {
61 char bytes[LINE_MAX];
62 size_t size;
63 size_t len;
64 };
65
66 static void linebuf_init(struct linebuf *buf) {
67 buf->size = 0;
68 buf->len = 0;
69 }
70
71 static const char *linebuf_get(struct linebuf *buf) {
72 if (buf->len + 1 >= sizeof(buf->bytes))
73 return NULL;
74 buf->bytes[buf->len] = '\0';
75 return buf->bytes;
76 }
77
78 static bool linebuf_add(struct linebuf *buf, const char *s, size_t len) {
79 if (buf->len + len >= sizeof(buf->bytes))
80 return false;
81 memcpy(buf->bytes + buf->len, s, len);
82 buf->len += len;
83 return true;
84 }
85
86 static bool linebuf_add_char(struct linebuf *buf, char c)
87 {
88 if (buf->len + 1 >= sizeof(buf->bytes))
89 return false;
90 buf->bytes[buf->len++] = c;
91 return true;
92 }
93
94 static void linebuf_rem(struct linebuf *buf, size_t count) {
95 assert(buf->len >= count);
96 buf->len -= count;
97 }
98
99 static void linebuf_rem_char(struct linebuf *buf) {
100 linebuf_rem(buf, 1);
101 }
102
103 static const struct trie_child_entry_f *trie_node_children(struct udev_hwdb *hwdb, const struct trie_node_f *node) {
104 return (const struct trie_child_entry_f *)((const char *)node + le64toh(hwdb->head->node_size));
105 }
106
107 static const struct trie_value_entry_f *trie_node_values(struct udev_hwdb *hwdb, const struct trie_node_f *node) {
108 const char *base = (const char *)node;
109
110 base += le64toh(hwdb->head->node_size);
111 base += node->children_count * le64toh(hwdb->head->child_entry_size);
112 return (const struct trie_value_entry_f *)base;
113 }
114
115 static const struct trie_node_f *trie_node_from_off(struct udev_hwdb *hwdb, le64_t off) {
116 return (const struct trie_node_f *)(hwdb->map + le64toh(off));
117 }
118
119 static const char *trie_string(struct udev_hwdb *hwdb, le64_t off) {
120 return hwdb->map + le64toh(off);
121 }
122
123 static int trie_children_cmp_f(const void *v1, const void *v2) {
124 const struct trie_child_entry_f *n1 = v1;
125 const struct trie_child_entry_f *n2 = v2;
126
127 return n1->c - n2->c;
128 }
129
130 static const struct trie_node_f *node_lookup_f(struct udev_hwdb *hwdb, const struct trie_node_f *node, uint8_t c) {
131 struct trie_child_entry_f *child;
132 struct trie_child_entry_f search;
133
134 search.c = c;
135 child = bsearch(&search, trie_node_children(hwdb, node), node->children_count,
136 le64toh(hwdb->head->child_entry_size), trie_children_cmp_f);
137 if (child)
138 return trie_node_from_off(hwdb, child->child_off);
139 return NULL;
140 }
141
142 static int hwdb_add_property(struct udev_hwdb *hwdb, const char *key, const char *value) {
143 /* TODO: add sub-matches (+) against DMI data */
144 if (key[0] != ' ')
145 return 0;
146 if (udev_list_entry_add(&hwdb->properties_list, key+1, value) == NULL)
147 return -ENOMEM;
148 return 0;
149 }
150
151 static int trie_fnmatch_f(struct udev_hwdb *hwdb, const struct trie_node_f *node, size_t p,
152 struct linebuf *buf, const char *search) {
153 size_t len;
154 size_t i;
155 const char *prefix;
156 int err;
157
158 prefix = trie_string(hwdb, node->prefix_off);
159 len = strlen(prefix + p);
160 linebuf_add(buf, prefix + p, len);
161
162 for (i = 0; i < node->children_count; i++) {
163 const struct trie_child_entry_f *child = &trie_node_children(hwdb, node)[i];
164
165 linebuf_add_char(buf, child->c);
166 err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search);
167 if (err < 0)
168 return err;
169 linebuf_rem_char(buf);
170 }
171
172 if (node->values_count && fnmatch(linebuf_get(buf), search, 0) == 0)
173 for (i = 0; i < node->values_count; i++) {
174 err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[i].key_off),
175 trie_string(hwdb, trie_node_values(hwdb, node)[i].value_off));
176 if (err < 0)
177 return err;
178 }
179
180 linebuf_rem(buf, len);
181 return 0;
182 }
183
184 static int trie_search_f(struct udev_hwdb *hwdb, const char *search) {
185 struct linebuf buf;
186 const struct trie_node_f *node;
187 size_t i = 0;
188 int err;
189
190 linebuf_init(&buf);
191
192 node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off);
193 while (node) {
194 const struct trie_node_f *child;
195 size_t p = 0;
196
197 if (node->prefix_off) {
198 uint8_t c;
199
200 for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) {
201 if (c == '*' || c == '?' || c == '[')
202 return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p);
203 if (c != search[i + p])
204 return 0;
205 }
206 i += p;
207 }
208
209 child = node_lookup_f(hwdb, node, '*');
210 if (child) {
211 linebuf_add_char(&buf, '*');
212 err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
213 if (err < 0)
214 return err;
215 linebuf_rem_char(&buf);
216 }
217
218 child = node_lookup_f(hwdb, node, '?');
219 if (child) {
220 linebuf_add_char(&buf, '?');
221 err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
222 if (err < 0)
223 return err;
224 linebuf_rem_char(&buf);
225 }
226
227 child = node_lookup_f(hwdb, node, '[');
228 if (child) {
229 linebuf_add_char(&buf, '[');
230 err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
231 if (err < 0)
232 return err;
233 linebuf_rem_char(&buf);
234 }
235
236 if (search[i] == '\0') {
237 size_t n;
238
239 for (n = 0; n < node->values_count; n++) {
240 err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[n].key_off),
241 trie_string(hwdb, trie_node_values(hwdb, node)[n].value_off));
242 if (err < 0)
243 return err;
244 }
245 return 0;
246 }
247
248 child = node_lookup_f(hwdb, node, search[i]);
249 node = child;
250 i++;
251 }
252 return 0;
253 }
254
255 /**
256 * udev_hwdb_new:
257 * @udev: udev library context
258 *
259 * Create a hardware database context to query properties for devices.
260 *
261 * Returns: a hwdb context.
262 **/
263 _public_ struct udev_hwdb *udev_hwdb_new(struct udev *udev) {
264 struct udev_hwdb *hwdb;
265 const char sig[] = HWDB_SIG;
266
267 hwdb = new0(struct udev_hwdb, 1);
268 if (!hwdb)
269 return NULL;
270
271 hwdb->refcount = 1;
272 udev_list_init(udev, &hwdb->properties_list, true);
273
274 hwdb->f = fopen(HWDB_BIN, "re");
275 if (!hwdb->f) {
276 log_debug("error reading %s: %m", HWDB_BIN);
277 udev_hwdb_unref(hwdb);
278 return NULL;
279 }
280
281 if (fstat(fileno(hwdb->f), &hwdb->st) < 0 ||
282 (size_t)hwdb->st.st_size < offsetof(struct trie_header_f, strings_len) + 8) {
283 log_debug("error reading %s: %m", HWDB_BIN);
284 udev_hwdb_unref(hwdb);
285 return NULL;
286 }
287
288 hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0);
289 if (hwdb->map == MAP_FAILED) {
290 log_debug("error mapping %s: %m", HWDB_BIN);
291 udev_hwdb_unref(hwdb);
292 return NULL;
293 }
294
295 if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 ||
296 (size_t)hwdb->st.st_size != le64toh(hwdb->head->file_size)) {
297 log_debug("error recognizing the format of %s", HWDB_BIN);
298 udev_hwdb_unref(hwdb);
299 return NULL;
300 }
301
302 log_debug("=== trie on-disk ===\n");
303 log_debug("tool version: %llu", (unsigned long long)le64toh(hwdb->head->tool_version));
304 log_debug("file size: %8llu bytes\n", (unsigned long long)hwdb->st.st_size);
305 log_debug("header size %8llu bytes\n", (unsigned long long)le64toh(hwdb->head->header_size));
306 log_debug("strings %8llu bytes\n", (unsigned long long)le64toh(hwdb->head->strings_len));
307 log_debug("nodes %8llu bytes\n", (unsigned long long)le64toh(hwdb->head->nodes_len));
308 return hwdb;
309 }
310
311 /**
312 * udev_hwdb_ref:
313 * @hwdb: context
314 *
315 * Take a reference of a hwdb context.
316 *
317 * Returns: the passed enumeration context
318 **/
319 _public_ struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb) {
320 if (!hwdb)
321 return NULL;
322 hwdb->refcount++;
323 return hwdb;
324 }
325
326 /**
327 * udev_hwdb_unref:
328 * @hwdb: context
329 *
330 * Drop a reference of a hwdb context. If the refcount reaches zero,
331 * all resources of the hwdb context will be released.
332 *
333 * Returns: the passed hwdb context if it has still an active reference, or #NULL otherwise.
334 **/
335 _public_ struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb) {
336 if (!hwdb)
337 return NULL;
338 hwdb->refcount--;
339 if (hwdb->refcount > 0)
340 return hwdb;
341 if (hwdb->map)
342 munmap((void *)hwdb->map, hwdb->st.st_size);
343 if (hwdb->f)
344 fclose(hwdb->f);
345 udev_list_cleanup(&hwdb->properties_list);
346 free(hwdb);
347 return NULL;
348 }
349
350 bool udev_hwdb_validate(struct udev_hwdb *hwdb) {
351 struct stat st;
352
353 if (!hwdb)
354 return false;
355 if (!hwdb->f)
356 return false;
357 if (fstat(fileno(hwdb->f), &st) < 0)
358 return true;
359 if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim))
360 return true;
361 return false;
362 }
363
364 /**
365 * udev_hwdb_get_properties_list_entry:
366 * @hwdb: context
367 * @modalias: modalias string
368 * @flags: (unused)
369 *
370 * Lookup a matching device in the hardware database. The lookup key is a
371 * modalias string, whose formats are defined for the Linux kernel modules.
372 * Examples are: pci:v00008086d00001C2D*, usb:v04F2pB221*. The first entry
373 * of a list of retrieved properties is returned.
374 *
375 * Returns: a udev_list_entry.
376 */
377 _public_ struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags) {
378 int err;
379
380 if (!hwdb || !hwdb->f) {
381 errno = EINVAL;
382 return NULL;
383 }
384
385 udev_list_cleanup(&hwdb->properties_list);
386 err = trie_search_f(hwdb, modalias);
387 if (err < 0) {
388 errno = -err;
389 return NULL;
390 }
391 return udev_list_get_entry(&hwdb->properties_list);
392 }