]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd/sd-hwdb/sd-hwdb.c
tree-wide: expose "p"-suffix unref calls in public APIs to make gcc cleanup easy
[thirdparty/systemd.git] / src / libsystemd / sd-hwdb / sd-hwdb.c
CommitLineData
23fbe14f
TG
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
23fbe14f 22#include <errno.h>
07630cea 23#include <fnmatch.h>
23fbe14f 24#include <inttypes.h>
07630cea 25#include <stdio.h>
23fbe14f 26#include <stdlib.h>
07630cea 27#include <string.h>
23fbe14f
TG
28#include <sys/mman.h>
29
30#include "sd-hwdb.h"
31
b5efdb8a 32#include "alloc-util.h"
3ffd4af2 33#include "fd-util.h"
23fbe14f 34#include "hashmap.h"
23fbe14f 35#include "hwdb-internal.h"
07630cea
LP
36#include "hwdb-util.h"
37#include "refcnt.h"
38#include "string-util.h"
23fbe14f
TG
39
40struct 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
58struct linebuf {
59 char bytes[LINE_MAX];
60 size_t size;
61 size_t len;
62};
63
64static void linebuf_init(struct linebuf *buf) {
65 buf->size = 0;
66 buf->len = 0;
67}
68
69static 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
76static 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
52efd56a 84static bool linebuf_add_char(struct linebuf *buf, char c) {
23fbe14f
TG
85 if (buf->len + 1 >= sizeof(buf->bytes))
86 return false;
87 buf->bytes[buf->len++] = c;
88 return true;
89}
90
91static void linebuf_rem(struct linebuf *buf, size_t count) {
92 assert(buf->len >= count);
93 buf->len -= count;
94}
95
96static void linebuf_rem_char(struct linebuf *buf) {
97 linebuf_rem(buf, 1);
98}
99
100static const struct trie_child_entry_f *trie_node_children(sd_hwdb *hwdb, const struct trie_node_f *node) {
101 return (const struct trie_child_entry_f *)((const char *)node + le64toh(hwdb->head->node_size));
102}
103
104static const struct trie_value_entry_f *trie_node_values(sd_hwdb *hwdb, const struct trie_node_f *node) {
105 const char *base = (const char *)node;
106
107 base += le64toh(hwdb->head->node_size);
108 base += node->children_count * le64toh(hwdb->head->child_entry_size);
109 return (const struct trie_value_entry_f *)base;
110}
111
112static const struct trie_node_f *trie_node_from_off(sd_hwdb *hwdb, le64_t off) {
113 return (const struct trie_node_f *)(hwdb->map + le64toh(off));
114}
115
116static const char *trie_string(sd_hwdb *hwdb, le64_t off) {
117 return hwdb->map + le64toh(off);
118}
119
120static int trie_children_cmp_f(const void *v1, const void *v2) {
121 const struct trie_child_entry_f *n1 = v1;
122 const struct trie_child_entry_f *n2 = v2;
123
124 return n1->c - n2->c;
125}
126
127static const struct trie_node_f *node_lookup_f(sd_hwdb *hwdb, const struct trie_node_f *node, uint8_t c) {
128 struct trie_child_entry_f *child;
129 struct trie_child_entry_f search;
130
131 search.c = c;
132 child = bsearch(&search, trie_node_children(hwdb, node), node->children_count,
133 le64toh(hwdb->head->child_entry_size), trie_children_cmp_f);
134 if (child)
135 return trie_node_from_off(hwdb, child->child_off);
136 return NULL;
137}
138
139static int hwdb_add_property(sd_hwdb *hwdb, const char *key, const char *value) {
140 int r;
141
142 assert(hwdb);
143 assert(key);
144 assert(value);
145
146 /*
147 * Silently ignore all properties which do not start with a
148 * space; future extensions might use additional prefixes.
149 */
150 if (key[0] != ' ')
151 return 0;
152
153 key++;
154
155 r = ordered_hashmap_ensure_allocated(&hwdb->properties, &string_hash_ops);
156 if (r < 0)
157 return r;
158
159 r = ordered_hashmap_replace(hwdb->properties, key, (char*)value);
160 if (r < 0)
161 return r;
162
163 hwdb->properties_modified = true;
164
165 return 0;
166}
167
168static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t p,
169 struct linebuf *buf, const char *search) {
170 size_t len;
171 size_t i;
172 const char *prefix;
173 int err;
174
175 prefix = trie_string(hwdb, node->prefix_off);
176 len = strlen(prefix + p);
177 linebuf_add(buf, prefix + p, len);
178
179 for (i = 0; i < node->children_count; i++) {
180 const struct trie_child_entry_f *child = &trie_node_children(hwdb, node)[i];
181
182 linebuf_add_char(buf, child->c);
183 err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search);
184 if (err < 0)
185 return err;
186 linebuf_rem_char(buf);
187 }
188
189 if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0)
190 for (i = 0; i < le64toh(node->values_count); i++) {
191 err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[i].key_off),
192 trie_string(hwdb, trie_node_values(hwdb, node)[i].value_off));
193 if (err < 0)
194 return err;
195 }
196
197 linebuf_rem(buf, len);
198 return 0;
199}
200
201static int trie_search_f(sd_hwdb *hwdb, const char *search) {
202 struct linebuf buf;
203 const struct trie_node_f *node;
204 size_t i = 0;
205 int err;
206
207 linebuf_init(&buf);
208
209 node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off);
210 while (node) {
211 const struct trie_node_f *child;
212 size_t p = 0;
213
214 if (node->prefix_off) {
215 uint8_t c;
216
217 for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) {
218 if (c == '*' || c == '?' || c == '[')
219 return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p);
220 if (c != search[i + p])
221 return 0;
222 }
223 i += p;
224 }
225
226 child = node_lookup_f(hwdb, node, '*');
227 if (child) {
228 linebuf_add_char(&buf, '*');
229 err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
230 if (err < 0)
231 return err;
232 linebuf_rem_char(&buf);
233 }
234
235 child = node_lookup_f(hwdb, node, '?');
236 if (child) {
237 linebuf_add_char(&buf, '?');
238 err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
239 if (err < 0)
240 return err;
241 linebuf_rem_char(&buf);
242 }
243
244 child = node_lookup_f(hwdb, node, '[');
245 if (child) {
246 linebuf_add_char(&buf, '[');
247 err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
248 if (err < 0)
249 return err;
250 linebuf_rem_char(&buf);
251 }
252
253 if (search[i] == '\0') {
254 size_t n;
255
256 for (n = 0; n < le64toh(node->values_count); n++) {
257 err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[n].key_off),
258 trie_string(hwdb, trie_node_values(hwdb, node)[n].value_off));
259 if (err < 0)
260 return err;
261 }
262 return 0;
263 }
264
265 child = node_lookup_f(hwdb, node, search[i]);
266 node = child;
267 i++;
268 }
269 return 0;
270}
271
272static const char hwdb_bin_paths[] =
52efd56a
LP
273 "/etc/systemd/hwdb/hwdb.bin\0"
274 "/etc/udev/hwdb.bin\0"
275 "/usr/lib/systemd/hwdb/hwdb.bin\0"
23fbe14f 276#ifdef HAVE_SPLIT_USR
52efd56a 277 "/lib/systemd/hwdb/hwdb.bin\0"
23fbe14f 278#endif
52efd56a 279 UDEVLIBEXECDIR "/hwdb.bin\0";
23fbe14f
TG
280
281_public_ int sd_hwdb_new(sd_hwdb **ret) {
4afd3348 282 _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
23fbe14f
TG
283 const char *hwdb_bin_path;
284 const char sig[] = HWDB_SIG;
285
286 assert_return(ret, -EINVAL);
287
288 hwdb = new0(sd_hwdb, 1);
289 if (!hwdb)
290 return -ENOMEM;
291
292 hwdb->n_ref = REFCNT_INIT;
293
294 /* find hwdb.bin in hwdb_bin_paths */
295 NULSTR_FOREACH(hwdb_bin_path, hwdb_bin_paths) {
296 hwdb->f = fopen(hwdb_bin_path, "re");
297 if (hwdb->f)
298 break;
299 else if (errno == ENOENT)
300 continue;
301 else
302 return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path);
303 }
304
305 if (!hwdb->f) {
306 log_debug("hwdb.bin does not exist, please run udevadm hwdb --update");
307 return -ENOENT;
308 }
309
310 if (fstat(fileno(hwdb->f), &hwdb->st) < 0 ||
311 (size_t)hwdb->st.st_size < offsetof(struct trie_header_f, strings_len) + 8)
312 return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path);
313
314 hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0);
315 if (hwdb->map == MAP_FAILED)
316 return log_debug_errno(errno, "error mapping %s: %m", hwdb_bin_path);
317
318 if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 ||
319 (size_t)hwdb->st.st_size != le64toh(hwdb->head->file_size)) {
320 log_debug("error recognizing the format of %s", hwdb_bin_path);
7e518afa 321 return -EINVAL;
23fbe14f
TG
322 }
323
324 log_debug("=== trie on-disk ===");
325 log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version));
1fa2f38f 326 log_debug("file size: %8"PRIi64" bytes", hwdb->st.st_size);
23fbe14f
TG
327 log_debug("header size %8"PRIu64" bytes", le64toh(hwdb->head->header_size));
328 log_debug("strings %8"PRIu64" bytes", le64toh(hwdb->head->strings_len));
329 log_debug("nodes %8"PRIu64" bytes", le64toh(hwdb->head->nodes_len));
330
331 *ret = hwdb;
332 hwdb = NULL;
333
334 return 0;
335}
336
337_public_ sd_hwdb *sd_hwdb_ref(sd_hwdb *hwdb) {
338 assert_return(hwdb, NULL);
339
340 assert_se(REFCNT_INC(hwdb->n_ref) >= 2);
341
342 return hwdb;
343}
344
345_public_ sd_hwdb *sd_hwdb_unref(sd_hwdb *hwdb) {
f0c4b1c3 346 if (hwdb && REFCNT_DEC(hwdb->n_ref) == 0) {
23fbe14f
TG
347 if (hwdb->map)
348 munmap((void *)hwdb->map, hwdb->st.st_size);
74ca738f 349 safe_fclose(hwdb->f);
23fbe14f
TG
350 free(hwdb->modalias);
351 ordered_hashmap_free(hwdb->properties);
352 free(hwdb);
353 }
354
355 return NULL;
356}
357
358bool 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
383static 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) {
8927b1da
DH
452 const void *k;
453 void *v;
23fbe14f
TG
454
455 assert_return(hwdb, -EINVAL);
456 assert_return(key, -EINVAL);
457 assert_return(value, -EINVAL);
458
459 if (hwdb->properties_modified)
460 return -EAGAIN;
461
8927b1da 462 ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, &v, &k);
23fbe14f
TG
463 if (!k)
464 return 0;
465
466 *key = k;
467 *value = v;
468
469 return 1;
470}