]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libudev/libudev-hwdb.c
treewide: use log_*_errno whenever %m is in the format string
[thirdparty/systemd.git] / src / libudev / libudev-hwdb.c
CommitLineData
2001208c
KS
1/***
2 This file is part of systemd.
3
88a6477e 4 Copyright 2012 Kay Sievers <kay@vrfy.org>
2001208c
KS
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 *
f2d433e1 38 * Libudev hardware database interface.
2001208c
KS
39 */
40
41/**
42 * udev_hwdb:
43 *
44 * Opaque object representing the hardware database.
45 */
46struct 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
60struct linebuf {
61 char bytes[LINE_MAX];
62 size_t size;
63 size_t len;
64};
65
66static void linebuf_init(struct linebuf *buf) {
67 buf->size = 0;
68 buf->len = 0;
69}
70
71static 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
78static 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
86static 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
94static void linebuf_rem(struct linebuf *buf, size_t count) {
95 assert(buf->len >= count);
96 buf->len -= count;
97}
98
99static void linebuf_rem_char(struct linebuf *buf) {
100 linebuf_rem(buf, 1);
101}
102
103static 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
107static 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
115static 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
119static const char *trie_string(struct udev_hwdb *hwdb, le64_t off) {
120 return hwdb->map + le64toh(off);
121}
122
123static 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
130static 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
142static int hwdb_add_property(struct udev_hwdb *hwdb, const char *key, const char *value) {
3cf7b686
KS
143 /*
144 * Silently ignore all properties which do not start with a
145 * space; future extensions might use additional prefixes.
146 */
2001208c
KS
147 if (key[0] != ' ')
148 return 0;
3cf7b686 149
2001208c
KS
150 if (udev_list_entry_add(&hwdb->properties_list, key+1, value) == NULL)
151 return -ENOMEM;
152 return 0;
153}
154
155static 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
f4443fa5
EB
176 if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0)
177 for (i = 0; i < le64toh(node->values_count); i++) {
2001208c
KS
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
188static 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
f4443fa5 243 for (n = 0; n < le64toh(node->values_count); n++) {
2001208c
KS
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
33488f19
MP
259static const char hwdb_bin_paths[] =
260 "/etc/udev/hwdb.bin\0"
261 UDEVLIBEXECDIR "/hwdb.bin\0";
262
263
2001208c
KS
264/**
265 * udev_hwdb_new:
266 * @udev: udev library context
267 *
268 * Create a hardware database context to query properties for devices.
269 *
270 * Returns: a hwdb context.
271 **/
272_public_ struct udev_hwdb *udev_hwdb_new(struct udev *udev) {
273 struct udev_hwdb *hwdb;
33488f19 274 const char *hwdb_bin_path;
2001208c
KS
275 const char sig[] = HWDB_SIG;
276
277 hwdb = new0(struct udev_hwdb, 1);
278 if (!hwdb)
279 return NULL;
280
281 hwdb->refcount = 1;
282 udev_list_init(udev, &hwdb->properties_list, true);
283
33488f19
MP
284 /* find hwdb.bin in hwdb_bin_paths */
285 NULSTR_FOREACH(hwdb_bin_path, hwdb_bin_paths) {
286 hwdb->f = fopen(hwdb_bin_path, "re");
287 if (hwdb->f)
288 break;
289 else if (errno == ENOENT)
290 continue;
291 else {
56f64d95 292 log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path);
33488f19
MP
293 udev_hwdb_unref(hwdb);
294 return NULL;
295 }
296 }
297
2001208c 298 if (!hwdb->f) {
25e773ee 299 log_debug("hwdb.bin does not exist, please run udevadm hwdb --update");
2001208c
KS
300 udev_hwdb_unref(hwdb);
301 return NULL;
302 }
303
304 if (fstat(fileno(hwdb->f), &hwdb->st) < 0 ||
305 (size_t)hwdb->st.st_size < offsetof(struct trie_header_f, strings_len) + 8) {
56f64d95 306 log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path);
2001208c
KS
307 udev_hwdb_unref(hwdb);
308 return NULL;
309 }
310
311 hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0);
312 if (hwdb->map == MAP_FAILED) {
56f64d95 313 log_debug_errno(errno, "error mapping %s: %m", hwdb_bin_path);
2001208c
KS
314 udev_hwdb_unref(hwdb);
315 return NULL;
316 }
317
318 if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 ||
319 (size_t)hwdb->st.st_size != le64toh(hwdb->head->file_size)) {
25e773ee 320 log_debug("error recognizing the format of %s", hwdb_bin_path);
2001208c
KS
321 udev_hwdb_unref(hwdb);
322 return NULL;
323 }
324
ff49bc32 325 log_debug("=== trie on-disk ===");
25e773ee 326 log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version));
ff49bc32
MS
327 log_debug("file size: %8"PRIu64" bytes", hwdb->st.st_size);
328 log_debug("header size %8"PRIu64" bytes", le64toh(hwdb->head->header_size));
329 log_debug("strings %8"PRIu64" bytes", le64toh(hwdb->head->strings_len));
330 log_debug("nodes %8"PRIu64" bytes", le64toh(hwdb->head->nodes_len));
2001208c
KS
331 return hwdb;
332}
333
334/**
335 * udev_hwdb_ref:
336 * @hwdb: context
337 *
338 * Take a reference of a hwdb context.
339 *
340 * Returns: the passed enumeration context
341 **/
342_public_ struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb) {
343 if (!hwdb)
344 return NULL;
345 hwdb->refcount++;
346 return hwdb;
347}
348
349/**
350 * udev_hwdb_unref:
351 * @hwdb: context
352 *
353 * Drop a reference of a hwdb context. If the refcount reaches zero,
354 * all resources of the hwdb context will be released.
355 *
725d7e6c 356 * Returns: #NULL
2001208c
KS
357 **/
358_public_ struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb) {
359 if (!hwdb)
360 return NULL;
361 hwdb->refcount--;
362 if (hwdb->refcount > 0)
725d7e6c 363 return NULL;
2001208c
KS
364 if (hwdb->map)
365 munmap((void *)hwdb->map, hwdb->st.st_size);
9485d98d
KS
366 if (hwdb->f)
367 fclose(hwdb->f);
2001208c
KS
368 udev_list_cleanup(&hwdb->properties_list);
369 free(hwdb);
370 return NULL;
371}
372
373bool udev_hwdb_validate(struct udev_hwdb *hwdb) {
33488f19
MP
374 bool found = false;
375 const char* p;
2001208c
KS
376 struct stat st;
377
378 if (!hwdb)
379 return false;
380 if (!hwdb->f)
381 return false;
33488f19
MP
382
383 /* if hwdb.bin doesn't exist anywhere, we need to update */
384 NULSTR_FOREACH(p, hwdb_bin_paths) {
385 if (stat(p, &st) >= 0) {
386 found = true;
387 break;
388 }
389 }
390 if (!found)
2001208c 391 return true;
33488f19 392
40fe8b11 393 if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim))
2001208c
KS
394 return true;
395 return false;
396}
397
398/**
399 * udev_hwdb_get_properties_list_entry:
400 * @hwdb: context
401 * @modalias: modalias string
402 * @flags: (unused)
403 *
404 * Lookup a matching device in the hardware database. The lookup key is a
405 * modalias string, whose formats are defined for the Linux kernel modules.
406 * Examples are: pci:v00008086d00001C2D*, usb:v04F2pB221*. The first entry
407 * of a list of retrieved properties is returned.
408 *
409 * Returns: a udev_list_entry.
410 */
411_public_ struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags) {
412 int err;
413
33c770b1 414 if (!hwdb || !hwdb->f) {
2001208c
KS
415 errno = EINVAL;
416 return NULL;
417 }
418
9485d98d 419 udev_list_cleanup(&hwdb->properties_list);
2001208c
KS
420 err = trie_search_f(hwdb, modalias);
421 if (err < 0) {
422 errno = -err;
423 return NULL;
424 }
425 return udev_list_get_entry(&hwdb->properties_list);
426}