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
linebuf_init(struct linebuf * buf)66 static void linebuf_init(struct linebuf *buf) {
67 buf->size = 0;
68 buf->len = 0;
69 }
70
linebuf_get(struct linebuf * buf)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
linebuf_add(struct linebuf * buf,const char * s,size_t len)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
linebuf_add_char(struct linebuf * buf,char c)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
linebuf_rem(struct linebuf * buf,size_t count)94 static void linebuf_rem(struct linebuf *buf, size_t count) {
95 assert(buf->len >= count);
96 buf->len -= count;
97 }
98
linebuf_rem_char(struct linebuf * buf)99 static void linebuf_rem_char(struct linebuf *buf) {
100 linebuf_rem(buf, 1);
101 }
102
trie_node_children(struct udev_hwdb * hwdb,const struct trie_node_f * node)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
trie_node_values(struct udev_hwdb * hwdb,const struct trie_node_f * node)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
trie_node_from_off(struct udev_hwdb * hwdb,le64_t off)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
trie_string(struct udev_hwdb * hwdb,le64_t off)119 static const char *trie_string(struct udev_hwdb *hwdb, le64_t off) {
120 return hwdb->map + le64toh(off);
121 }
122
trie_children_cmp_f(const void * v1,const void * v2)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
node_lookup_f(struct udev_hwdb * hwdb,const struct trie_node_f * node,uint8_t c)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
hwdb_add_property(struct udev_hwdb * hwdb,const char * key,const char * value)142 static int hwdb_add_property(struct udev_hwdb *hwdb, const char *key, const char *value) {
143 /*
144 * Silently ignore all properties which do not start with a
145 * space; future extensions might use additional prefixes.
146 */
147 if (key[0] != ' ')
148 return 0;
149
150 if (udev_list_entry_add(&hwdb->properties_list, key+1, value) == NULL)
151 return -ENOMEM;
152 return 0;
153 }
154
trie_fnmatch_f(struct udev_hwdb * hwdb,const struct trie_node_f * node,size_t p,struct linebuf * buf,const char * search)155 static int trie_fnmatch_f(struct udev_hwdb *hwdb, const struct trie_node_f *node, size_t p,
156 struct linebuf *buf, const char *search) {
157 size_t len;
158 size_t i;
159 const char *prefix;
160 int err;
161
162 prefix = trie_string(hwdb, node->prefix_off);
163 len = strlen(prefix + p);
164 linebuf_add(buf, prefix + p, len);
165
166 for (i = 0; i < node->children_count; i++) {
167 const struct trie_child_entry_f *child = &trie_node_children(hwdb, node)[i];
168
169 linebuf_add_char(buf, child->c);
170 err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search);
171 if (err < 0)
172 return err;
173 linebuf_rem_char(buf);
174 }
175
176 if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0)
177 for (i = 0; i < le64toh(node->values_count); i++) {
178 err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[i].key_off),
179 trie_string(hwdb, trie_node_values(hwdb, node)[i].value_off));
180 if (err < 0)
181 return err;
182 }
183
184 linebuf_rem(buf, len);
185 return 0;
186 }
187
trie_search_f(struct udev_hwdb * hwdb,const char * search)188 static int trie_search_f(struct udev_hwdb *hwdb, const char *search) {
189 struct linebuf buf;
190 const struct trie_node_f *node;
191 size_t i = 0;
192 int err;
193
194 linebuf_init(&buf);
195
196 node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off);
197 while (node) {
198 const struct trie_node_f *child;
199 size_t p = 0;
200
201 if (node->prefix_off) {
202 uint8_t c;
203
204 for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) {
205 if (c == '*' || c == '?' || c == '[')
206 return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p);
207 if (c != search[i + p])
208 return 0;
209 }
210 i += p;
211 }
212
213 child = node_lookup_f(hwdb, node, '*');
214 if (child) {
215 linebuf_add_char(&buf, '*');
216 err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
217 if (err < 0)
218 return err;
219 linebuf_rem_char(&buf);
220 }
221
222 child = node_lookup_f(hwdb, node, '?');
223 if (child) {
224 linebuf_add_char(&buf, '?');
225 err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
226 if (err < 0)
227 return err;
228 linebuf_rem_char(&buf);
229 }
230
231 child = node_lookup_f(hwdb, node, '[');
232 if (child) {
233 linebuf_add_char(&buf, '[');
234 err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
235 if (err < 0)
236 return err;
237 linebuf_rem_char(&buf);
238 }
239
240 if (search[i] == '\0') {
241 size_t n;
242
243 for (n = 0; n < le64toh(node->values_count); n++) {
244 err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[n].key_off),
245 trie_string(hwdb, trie_node_values(hwdb, node)[n].value_off));
246 if (err < 0)
247 return err;
248 }
249 return 0;
250 }
251
252 child = node_lookup_f(hwdb, node, search[i]);
253 node = child;
254 i++;
255 }
256 return 0;
257 }
258
259 /**
260 * udev_hwdb_new:
261 * @udev: udev library context
262 *
263 * Create a hardware database context to query properties for devices.
264 *
265 * Returns: a hwdb context.
266 **/
udev_hwdb_new(struct udev * udev)267 _public_ struct udev_hwdb *udev_hwdb_new(struct udev *udev) {
268 struct udev_hwdb *hwdb;
269 const char sig[] = HWDB_SIG;
270
271 hwdb = new0(struct udev_hwdb, 1);
272 if (!hwdb)
273 return NULL;
274
275 hwdb->refcount = 1;
276 udev_list_init(udev, &hwdb->properties_list, true);
277
278 hwdb->f = fopen(UDEV_HWDB_BIN, "re");
279 if (!hwdb->f) {
280 log_debug(UDEV_HWDB_BIN " does not exist, please run udevadm hwdb --update");
281 udev_hwdb_unref(hwdb);
282 return NULL;
283 }
284
285 if (fstat(fileno(hwdb->f), &hwdb->st) < 0 ||
286 (size_t)hwdb->st.st_size < offsetof(struct trie_header_f, strings_len) + 8) {
287 log_debug_errno(errno, "error reading " UDEV_HWDB_BIN ": %m");
288 udev_hwdb_unref(hwdb);
289 return NULL;
290 }
291
292 hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0);
293 if (hwdb->map == MAP_FAILED) {
294 log_debug_errno(errno, "error mapping " UDEV_HWDB_BIN ": %m");
295 udev_hwdb_unref(hwdb);
296 return NULL;
297 }
298
299 if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 ||
300 (size_t)hwdb->st.st_size != le64toh(hwdb->head->file_size)) {
301 log_debug("error recognizing the format of " UDEV_HWDB_BIN);
302 udev_hwdb_unref(hwdb);
303 return NULL;
304 }
305
306 log_debug("=== trie on-disk ===");
307 log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version));
308 log_debug("file size: %8"PRIu64" bytes", hwdb->st.st_size);
309 log_debug("header size %8"PRIu64" bytes", le64toh(hwdb->head->header_size));
310 log_debug("strings %8"PRIu64" bytes", le64toh(hwdb->head->strings_len));
311 log_debug("nodes %8"PRIu64" bytes", le64toh(hwdb->head->nodes_len));
312 return hwdb;
313 }
314
315 /**
316 * udev_hwdb_ref:
317 * @hwdb: context
318 *
319 * Take a reference of a hwdb context.
320 *
321 * Returns: the passed enumeration context
322 **/
udev_hwdb_ref(struct udev_hwdb * hwdb)323 _public_ struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb) {
324 if (!hwdb)
325 return NULL;
326 hwdb->refcount++;
327 return hwdb;
328 }
329
330 /**
331 * udev_hwdb_unref:
332 * @hwdb: context
333 *
334 * Drop a reference of a hwdb context. If the refcount reaches zero,
335 * all resources of the hwdb context will be released.
336 *
337 * Returns: #NULL
338 **/
udev_hwdb_unref(struct udev_hwdb * hwdb)339 _public_ struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb) {
340 if (!hwdb)
341 return NULL;
342 hwdb->refcount--;
343 if (hwdb->refcount > 0)
344 return NULL;
345 if (hwdb->map)
346 munmap((void *)hwdb->map, hwdb->st.st_size);
347 if (hwdb->f)
348 fclose(hwdb->f);
349 udev_list_cleanup(&hwdb->properties_list);
350 free(hwdb);
351 return NULL;
352 }
353
udev_hwdb_validate(struct udev_hwdb * hwdb)354 bool udev_hwdb_validate(struct udev_hwdb *hwdb) {
355 struct stat st;
356
357 if (!hwdb)
358 return false;
359 if (!hwdb->f)
360 return false;
361 if (stat("/etc/udev/hwdb.bin", &st) < 0)
362 return true;
363
364 if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim))
365 return true;
366 return false;
367 }
368
369 /**
370 * udev_hwdb_get_properties_list_entry:
371 * @hwdb: context
372 * @modalias: modalias string
373 * @flags: (unused)
374 *
375 * Lookup a matching device in the hardware database. The lookup key is a
376 * modalias string, whose formats are defined for the Linux kernel modules.
377 * Examples are: pci:v00008086d00001C2D*, usb:v04F2pB221*. The first entry
378 * of a list of retrieved properties is returned.
379 *
380 * Returns: a udev_list_entry.
381 */
udev_hwdb_get_properties_list_entry(struct udev_hwdb * hwdb,const char * modalias,unsigned int flags)382 _public_ struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags) {
383 int err;
384
385 if (!hwdb || !hwdb->f) {
386 errno = EINVAL;
387 return NULL;
388 }
389
390 udev_list_cleanup(&hwdb->properties_list);
391 err = trie_search_f(hwdb, modalias);
392 if (err < 0) {
393 errno = -err;
394 return NULL;
395 }
396 return udev_list_get_entry(&hwdb->properties_list);
397 }
398