chiark / gitweb /
libudev: fix hwdb validation to look for the *new* file
[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 /**
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  **/
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("/etc/udev/hwdb.bin", "re");
279         if (!hwdb->f) {
280                 log_debug("error reading /etc/udev/hwdb.bin: %m");
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("error reading /etc/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("error mapping /etc/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 /etc/udev/hwdb.bin");
302                 udev_hwdb_unref(hwdb);
303                 return NULL;
304         }
305
306         log_debug("=== trie on-disk ===\n");
307         log_debug("tool version:          %"PRIu64, le64toh(hwdb->head->tool_version));
308         log_debug("file size:        %8llu bytes\n", (unsigned long long) hwdb->st.st_size);
309         log_debug("header size       %8"PRIu64" bytes\n", le64toh(hwdb->head->header_size));
310         log_debug("strings           %8"PRIu64" bytes\n", le64toh(hwdb->head->strings_len));
311         log_debug("nodes             %8"PRIu64" bytes\n", 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  **/
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: the passed hwdb context if it has still an active reference, or #NULL otherwise.
338  **/
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 hwdb;
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
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         if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim))
364                 return true;
365         return false;
366 }
367
368 /**
369  * udev_hwdb_get_properties_list_entry:
370  * @hwdb: context
371  * @modalias: modalias string
372  * @flags: (unused)
373  *
374  * Lookup a matching device in the hardware database. The lookup key is a
375  * modalias string, whose formats are defined for the Linux kernel modules.
376  * Examples are: pci:v00008086d00001C2D*, usb:v04F2pB221*. The first entry
377  * of a list of retrieved properties is returned.
378  *
379  * Returns: a udev_list_entry.
380  */
381 _public_ struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags) {
382         int err;
383
384         if (!hwdb || !hwdb->f) {
385                 errno = EINVAL;
386                 return NULL;
387         }
388
389         udev_list_cleanup(&hwdb->properties_list);
390         err = trie_search_f(hwdb, modalias);
391         if (err < 0) {
392                 errno = -err;
393                 return NULL;
394         }
395         return udev_list_get_entry(&hwdb->properties_list);
396 }