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