chiark / gitweb /
2a0e00f7d234a5b5b10f37c2735964df5dd56124
[elogind.git] / src / libelogind / sd-hwdb / sd-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   Copyright 2014 Tom Gundersen <teg@jklm.no>
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdio.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <inttypes.h>
26 #include <stdlib.h>
27 #include <fnmatch.h>
28 #include <sys/mman.h>
29
30 #include "sd-hwdb.h"
31
32 #include "hashmap.h"
33 #include "refcnt.h"
34
35 #include "hwdb-util.h"
36 #include "hwdb-internal.h"
37
38 struct sd_hwdb {
39         RefCount n_ref;
40         int refcount;
41
42         FILE *f;
43         struct stat st;
44         union {
45                 struct trie_header_f *head;
46                 const char *map;
47         };
48
49         char *modalias;
50
51         OrderedHashmap *properties;
52         Iterator properties_iterator;
53         bool properties_modified;
54 };
55
56 struct linebuf {
57         char bytes[LINE_MAX];
58         size_t size;
59         size_t len;
60 };
61
62 static void linebuf_init(struct linebuf *buf) {
63         buf->size = 0;
64         buf->len = 0;
65 }
66
67 static const char *linebuf_get(struct linebuf *buf) {
68         if (buf->len + 1 >= sizeof(buf->bytes))
69                 return NULL;
70         buf->bytes[buf->len] = '\0';
71         return buf->bytes;
72 }
73
74 static bool linebuf_add(struct linebuf *buf, const char *s, size_t len) {
75         if (buf->len + len >= sizeof(buf->bytes))
76                 return false;
77         memcpy(buf->bytes + buf->len, s, len);
78         buf->len += len;
79         return true;
80 }
81
82 static bool linebuf_add_char(struct linebuf *buf, char c)
83 {
84         if (buf->len + 1 >= sizeof(buf->bytes))
85                 return false;
86         buf->bytes[buf->len++] = c;
87         return true;
88 }
89
90 static void linebuf_rem(struct linebuf *buf, size_t count) {
91         assert(buf->len >= count);
92         buf->len -= count;
93 }
94
95 static void linebuf_rem_char(struct linebuf *buf) {
96         linebuf_rem(buf, 1);
97 }
98
99 static const struct trie_child_entry_f *trie_node_children(sd_hwdb *hwdb, const struct trie_node_f *node) {
100         return (const struct trie_child_entry_f *)((const char *)node + le64toh(hwdb->head->node_size));
101 }
102
103 static const struct trie_value_entry_f *trie_node_values(sd_hwdb *hwdb, const struct trie_node_f *node) {
104         const char *base = (const char *)node;
105
106         base += le64toh(hwdb->head->node_size);
107         base += node->children_count * le64toh(hwdb->head->child_entry_size);
108         return (const struct trie_value_entry_f *)base;
109 }
110
111 static const struct trie_node_f *trie_node_from_off(sd_hwdb *hwdb, le64_t off) {
112         return (const struct trie_node_f *)(hwdb->map + le64toh(off));
113 }
114
115 static const char *trie_string(sd_hwdb *hwdb, le64_t off) {
116         return hwdb->map + le64toh(off);
117 }
118
119 static int trie_children_cmp_f(const void *v1, const void *v2) {
120         const struct trie_child_entry_f *n1 = v1;
121         const struct trie_child_entry_f *n2 = v2;
122
123         return n1->c - n2->c;
124 }
125
126 static const struct trie_node_f *node_lookup_f(sd_hwdb *hwdb, const struct trie_node_f *node, uint8_t c) {
127         struct trie_child_entry_f *child;
128         struct trie_child_entry_f search;
129
130         search.c = c;
131         child = bsearch(&search, trie_node_children(hwdb, node), node->children_count,
132                         le64toh(hwdb->head->child_entry_size), trie_children_cmp_f);
133         if (child)
134                 return trie_node_from_off(hwdb, child->child_off);
135         return NULL;
136 }
137
138 static int hwdb_add_property(sd_hwdb *hwdb, const char *key, const char *value) {
139         int r;
140
141         assert(hwdb);
142         assert(key);
143         assert(value);
144
145         /*
146          * Silently ignore all properties which do not start with a
147          * space; future extensions might use additional prefixes.
148          */
149         if (key[0] != ' ')
150                 return 0;
151
152         key++;
153
154         r = ordered_hashmap_ensure_allocated(&hwdb->properties, &string_hash_ops);
155         if (r < 0)
156                 return r;
157
158         r = ordered_hashmap_replace(hwdb->properties, key, (char*)value);
159         if (r < 0)
160                 return r;
161
162         hwdb->properties_modified = true;
163
164         return 0;
165 }
166
167 static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t p,
168                           struct linebuf *buf, const char *search) {
169         size_t len;
170         size_t i;
171         const char *prefix;
172         int err;
173
174         prefix = trie_string(hwdb, node->prefix_off);
175         len = strlen(prefix + p);
176         linebuf_add(buf, prefix + p, len);
177
178         for (i = 0; i < node->children_count; i++) {
179                 const struct trie_child_entry_f *child = &trie_node_children(hwdb, node)[i];
180
181                 linebuf_add_char(buf, child->c);
182                 err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search);
183                 if (err < 0)
184                         return err;
185                 linebuf_rem_char(buf);
186         }
187
188         if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0)
189                 for (i = 0; i < le64toh(node->values_count); i++) {
190                         err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[i].key_off),
191                                                 trie_string(hwdb, trie_node_values(hwdb, node)[i].value_off));
192                         if (err < 0)
193                                 return err;
194                 }
195
196         linebuf_rem(buf, len);
197         return 0;
198 }
199
200 static int trie_search_f(sd_hwdb *hwdb, const char *search) {
201         struct linebuf buf;
202         const struct trie_node_f *node;
203         size_t i = 0;
204         int err;
205
206         linebuf_init(&buf);
207
208         node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off);
209         while (node) {
210                 const struct trie_node_f *child;
211                 size_t p = 0;
212
213                 if (node->prefix_off) {
214                         uint8_t c;
215
216                         for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) {
217                                 if (c == '*' || c == '?' || c == '[')
218                                         return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p);
219                                 if (c != search[i + p])
220                                         return 0;
221                         }
222                         i += p;
223                 }
224
225                 child = node_lookup_f(hwdb, node, '*');
226                 if (child) {
227                         linebuf_add_char(&buf, '*');
228                         err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
229                         if (err < 0)
230                                 return err;
231                         linebuf_rem_char(&buf);
232                 }
233
234                 child = node_lookup_f(hwdb, node, '?');
235                 if (child) {
236                         linebuf_add_char(&buf, '?');
237                         err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
238                         if (err < 0)
239                                 return err;
240                         linebuf_rem_char(&buf);
241                 }
242
243                 child = node_lookup_f(hwdb, node, '[');
244                 if (child) {
245                         linebuf_add_char(&buf, '[');
246                         err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
247                         if (err < 0)
248                                 return err;
249                         linebuf_rem_char(&buf);
250                 }
251
252                 if (search[i] == '\0') {
253                         size_t n;
254
255                         for (n = 0; n < le64toh(node->values_count); n++) {
256                                 err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[n].key_off),
257                                                         trie_string(hwdb, trie_node_values(hwdb, node)[n].value_off));
258                                 if (err < 0)
259                                         return err;
260                         }
261                         return 0;
262                 }
263
264                 child = node_lookup_f(hwdb, node, search[i]);
265                 node = child;
266                 i++;
267         }
268         return 0;
269 }
270
271 static const char hwdb_bin_paths[] =
272     "/etc/systemd/hwdb/hwdb.bin\0"
273     "/etc/udev/hwdb.bin\0"
274     "/usr/lib/systemd/hwdb/hwdb.bin\0"
275 #ifdef HAVE_SPLIT_USR
276     "/lib/systemd/hwdb/hwdb.bin\0"
277 #endif
278     UDEVLIBEXECDIR "/hwdb.bin\0";
279
280 _public_ int sd_hwdb_new(sd_hwdb **ret) {
281         _cleanup_hwdb_unref_ sd_hwdb *hwdb = NULL;
282         const char *hwdb_bin_path;
283         const char sig[] = HWDB_SIG;
284
285         assert_return(ret, -EINVAL);
286
287         hwdb = new0(sd_hwdb, 1);
288         if (!hwdb)
289                 return -ENOMEM;
290
291         hwdb->n_ref = REFCNT_INIT;
292
293         /* find hwdb.bin in hwdb_bin_paths */
294         NULSTR_FOREACH(hwdb_bin_path, hwdb_bin_paths) {
295                 hwdb->f = fopen(hwdb_bin_path, "re");
296                 if (hwdb->f)
297                         break;
298                 else if (errno == ENOENT)
299                         continue;
300                 else
301                         return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path);
302         }
303
304         if (!hwdb->f) {
305                 log_debug("hwdb.bin does not exist, please run udevadm hwdb --update");
306                 return -ENOENT;
307         }
308
309         if (fstat(fileno(hwdb->f), &hwdb->st) < 0 ||
310             (size_t)hwdb->st.st_size < offsetof(struct trie_header_f, strings_len) + 8)
311                 return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path);
312
313         hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0);
314         if (hwdb->map == MAP_FAILED)
315                 return log_debug_errno(errno, "error mapping %s: %m", hwdb_bin_path);
316
317         if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 ||
318             (size_t)hwdb->st.st_size != le64toh(hwdb->head->file_size)) {
319                 log_debug("error recognizing the format of %s", hwdb_bin_path);
320                 return -EINVAL;
321         }
322
323         log_debug("=== trie on-disk ===");
324         log_debug("tool version:          %"PRIu64, le64toh(hwdb->head->tool_version));
325         log_debug("file size:        %8"PRIi64" bytes", hwdb->st.st_size);
326         log_debug("header size       %8"PRIu64" bytes", le64toh(hwdb->head->header_size));
327         log_debug("strings           %8"PRIu64" bytes", le64toh(hwdb->head->strings_len));
328         log_debug("nodes             %8"PRIu64" bytes", le64toh(hwdb->head->nodes_len));
329
330         *ret = hwdb;
331         hwdb = NULL;
332
333         return 0;
334 }
335
336 _public_ sd_hwdb *sd_hwdb_ref(sd_hwdb *hwdb) {
337         assert_return(hwdb, NULL);
338
339         assert_se(REFCNT_INC(hwdb->n_ref) >= 2);
340
341         return hwdb;
342 }
343
344 _public_ sd_hwdb *sd_hwdb_unref(sd_hwdb *hwdb) {
345         if (hwdb && REFCNT_DEC(hwdb->n_ref) == 0) {
346                 if (hwdb->map)
347                         munmap((void *)hwdb->map, hwdb->st.st_size);
348                 if (hwdb->f)
349                         fclose(hwdb->f);
350                 free(hwdb->modalias);
351                 ordered_hashmap_free(hwdb->properties);
352                 free(hwdb);
353         }
354
355         return NULL;
356 }
357
358 bool hwdb_validate(sd_hwdb *hwdb) {
359         bool found = false;
360         const char* p;
361         struct stat st;
362
363         if (!hwdb)
364                 return false;
365         if (!hwdb->f)
366                 return false;
367
368         /* if hwdb.bin doesn't exist anywhere, we need to update */
369         NULSTR_FOREACH(p, hwdb_bin_paths) {
370                 if (stat(p, &st) >= 0) {
371                         found = true;
372                         break;
373                 }
374         }
375         if (!found)
376                 return true;
377
378         if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim))
379                 return true;
380         return false;
381 }
382
383 static int properties_prepare(sd_hwdb *hwdb, const char *modalias) {
384         _cleanup_free_ char *mod = NULL;
385         int r;
386
387         assert(hwdb);
388         assert(modalias);
389
390         if (streq_ptr(modalias, hwdb->modalias))
391                 return 0;
392
393         mod = strdup(modalias);
394         if (!mod)
395                 return -ENOMEM;
396
397         ordered_hashmap_clear(hwdb->properties);
398
399         hwdb->properties_modified = true;
400
401         r = trie_search_f(hwdb, modalias);
402         if (r < 0)
403                 return r;
404
405         free(hwdb->modalias);
406         hwdb->modalias = mod;
407         mod = NULL;
408
409         return 0;
410 }
411
412 _public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **_value) {
413         const char *value;
414         int r;
415
416         assert_return(hwdb, -EINVAL);
417         assert_return(hwdb->f, -EINVAL);
418         assert_return(modalias, -EINVAL);
419         assert_return(_value, -EINVAL);
420
421         r = properties_prepare(hwdb, modalias);
422         if (r < 0)
423                 return r;
424
425         value = ordered_hashmap_get(hwdb->properties, key);
426         if (!value)
427                 return -ENOENT;
428
429         *_value = value;
430
431         return 0;
432 }
433
434 _public_ int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias) {
435         int r;
436
437         assert_return(hwdb, -EINVAL);
438         assert_return(hwdb->f, -EINVAL);
439         assert_return(modalias, -EINVAL);
440
441         r = properties_prepare(hwdb, modalias);
442         if (r < 0)
443                 return r;
444
445         hwdb->properties_modified = false;
446         hwdb->properties_iterator = ITERATOR_FIRST;
447
448         return 0;
449 }
450
451 _public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value) {
452         const void *k, *v;
453
454         assert_return(hwdb, -EINVAL);
455         assert_return(key, -EINVAL);
456         assert_return(value, -EINVAL);
457
458         if (hwdb->properties_modified)
459                 return -EAGAIN;
460
461         v = ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, &k);
462         if (!k)
463                 return 0;
464
465         *key = k;
466         *value = v;
467
468         return 1;
469 }