chiark / gitweb /
bus: properly unmap mapped area
[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         /* 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 (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0)
173                 for (i = 0; i < le64toh(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 < le64toh(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("/etc/udev/hwdb.bin", "re");
275         if (!hwdb->f) {
276                 log_debug("error reading /etc/udev/hwdb.bin: %m");
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 /etc/udev/hwdb.bin: %m");
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 /etc/udev/hwdb.bin: %m");
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 /etc/udev/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 }