• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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