chiark / gitweb /
treewide: use log_*_errno whenever %m is in the format string
[elogind.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         /*
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
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
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 static const char hwdb_bin_paths[] =
260     "/etc/udev/hwdb.bin\0"
261     UDEVLIBEXECDIR "/hwdb.bin\0";
262
263
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;
274         const char *hwdb_bin_path;
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
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 {
292                         log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path);
293                         udev_hwdb_unref(hwdb);
294                         return NULL;
295                 }
296         }
297
298         if (!hwdb->f) {
299                 log_debug("hwdb.bin does not exist, please run udevadm hwdb --update");
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) {
306                 log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path);
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) {
313                 log_debug_errno(errno, "error mapping %s: %m", hwdb_bin_path);
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)) {
320                 log_debug("error recognizing the format of %s", hwdb_bin_path);
321                 udev_hwdb_unref(hwdb);
322                 return NULL;
323         }
324
325         log_debug("=== trie on-disk ===");
326         log_debug("tool version:          %"PRIu64, le64toh(hwdb->head->tool_version));
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));
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  *
356  * Returns: #NULL
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)
363                 return NULL;
364         if (hwdb->map)
365                 munmap((void *)hwdb->map, hwdb->st.st_size);
366         if (hwdb->f)
367                 fclose(hwdb->f);
368         udev_list_cleanup(&hwdb->properties_list);
369         free(hwdb);
370         return NULL;
371 }
372
373 bool udev_hwdb_validate(struct udev_hwdb *hwdb) {
374         bool found = false;
375         const char* p;
376         struct stat st;
377
378         if (!hwdb)
379                 return false;
380         if (!hwdb->f)
381                 return false;
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)
391                 return true;
392
393         if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim))
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
414         if (!hwdb || !hwdb->f) {
415                 errno = EINVAL;
416                 return NULL;
417         }
418
419         udev_list_cleanup(&hwdb->properties_list);
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 }