]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd/sd-hwdb/sd-hwdb.c
Merge pull request #4745 from joukewitteveen/notify
[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
8bf97636
DH
100static const struct trie_child_entry_f *trie_node_child(sd_hwdb *hwdb, const struct trie_node_f *node, size_t idx) {
101 const char *base = (const char *)node;
102
103 base += le64toh(hwdb->head->node_size);
104 base += idx * le64toh(hwdb->head->child_entry_size);
105 return (const struct trie_child_entry_f *)base;
23fbe14f
TG
106}
107
8bf97636 108static const struct trie_value_entry_f *trie_node_value(sd_hwdb *hwdb, const struct trie_node_f *node, size_t idx) {
23fbe14f
TG
109 const char *base = (const char *)node;
110
111 base += le64toh(hwdb->head->node_size);
112 base += node->children_count * le64toh(hwdb->head->child_entry_size);
8bf97636 113 base += idx * le64toh(hwdb->head->value_entry_size);
23fbe14f
TG
114 return (const struct trie_value_entry_f *)base;
115}
116
117static const struct trie_node_f *trie_node_from_off(sd_hwdb *hwdb, le64_t off) {
118 return (const struct trie_node_f *)(hwdb->map + le64toh(off));
119}
120
121static const char *trie_string(sd_hwdb *hwdb, le64_t off) {
122 return hwdb->map + le64toh(off);
123}
124
125static int trie_children_cmp_f(const void *v1, const void *v2) {
126 const struct trie_child_entry_f *n1 = v1;
127 const struct trie_child_entry_f *n2 = v2;
128
129 return n1->c - n2->c;
130}
131
132static const struct trie_node_f *node_lookup_f(sd_hwdb *hwdb, const struct trie_node_f *node, uint8_t c) {
133 struct trie_child_entry_f *child;
134 struct trie_child_entry_f search;
135
136 search.c = c;
8bf97636 137 child = bsearch(&search, (const char *)node + le64toh(hwdb->head->node_size), node->children_count,
23fbe14f
TG
138 le64toh(hwdb->head->child_entry_size), trie_children_cmp_f);
139 if (child)
140 return trie_node_from_off(hwdb, child->child_off);
141 return NULL;
142}
143
3a04b789
DH
144static int hwdb_add_property(sd_hwdb *hwdb, const struct trie_value_entry_f *entry) {
145 const char *key;
23fbe14f
TG
146 int r;
147
148 assert(hwdb);
3a04b789
DH
149
150 key = trie_string(hwdb, entry->key_off);
23fbe14f
TG
151
152 /*
153 * Silently ignore all properties which do not start with a
154 * space; future extensions might use additional prefixes.
155 */
156 if (key[0] != ' ')
157 return 0;
158
159 key++;
160
3a04b789
DH
161 if (le64toh(hwdb->head->value_entry_size) >= sizeof(struct trie_value_entry2_f)) {
162 const struct trie_value_entry2_f *old, *entry2;
163
164 entry2 = (const struct trie_value_entry2_f *)entry;
165 old = ordered_hashmap_get(hwdb->properties, key);
166 if (old) {
167 /* on duplicates, we order by filename and line-number */
168 r = strcmp(trie_string(hwdb, entry2->filename_off), trie_string(hwdb, old->filename_off));
169 if (r < 0 ||
170 (r == 0 && entry2->line_number < old->line_number))
171 return 0;
172 }
173 }
174
23fbe14f
TG
175 r = ordered_hashmap_ensure_allocated(&hwdb->properties, &string_hash_ops);
176 if (r < 0)
177 return r;
178
3a04b789 179 r = ordered_hashmap_replace(hwdb->properties, key, (void *)entry);
23fbe14f
TG
180 if (r < 0)
181 return r;
182
183 hwdb->properties_modified = true;
184
185 return 0;
186}
187
188static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t p,
189 struct linebuf *buf, const char *search) {
190 size_t len;
191 size_t i;
192 const char *prefix;
193 int err;
194
195 prefix = trie_string(hwdb, node->prefix_off);
196 len = strlen(prefix + p);
197 linebuf_add(buf, prefix + p, len);
198
199 for (i = 0; i < node->children_count; i++) {
8bf97636 200 const struct trie_child_entry_f *child = trie_node_child(hwdb, node, i);
23fbe14f
TG
201
202 linebuf_add_char(buf, child->c);
203 err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search);
204 if (err < 0)
205 return err;
206 linebuf_rem_char(buf);
207 }
208
209 if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0)
210 for (i = 0; i < le64toh(node->values_count); i++) {
3a04b789 211 err = hwdb_add_property(hwdb, trie_node_value(hwdb, node, i));
23fbe14f
TG
212 if (err < 0)
213 return err;
214 }
215
216 linebuf_rem(buf, len);
217 return 0;
218}
219
220static int trie_search_f(sd_hwdb *hwdb, const char *search) {
221 struct linebuf buf;
222 const struct trie_node_f *node;
223 size_t i = 0;
224 int err;
225
226 linebuf_init(&buf);
227
228 node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off);
229 while (node) {
230 const struct trie_node_f *child;
231 size_t p = 0;
232
233 if (node->prefix_off) {
234 uint8_t c;
235
236 for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) {
237 if (c == '*' || c == '?' || c == '[')
238 return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p);
239 if (c != search[i + p])
240 return 0;
241 }
242 i += p;
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 child = node_lookup_f(hwdb, node, '?');
255 if (child) {
256 linebuf_add_char(&buf, '?');
257 err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
258 if (err < 0)
259 return err;
260 linebuf_rem_char(&buf);
261 }
262
263 child = node_lookup_f(hwdb, node, '[');
264 if (child) {
265 linebuf_add_char(&buf, '[');
266 err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
267 if (err < 0)
268 return err;
269 linebuf_rem_char(&buf);
270 }
271
272 if (search[i] == '\0') {
273 size_t n;
274
275 for (n = 0; n < le64toh(node->values_count); n++) {
3a04b789 276 err = hwdb_add_property(hwdb, trie_node_value(hwdb, node, n));
23fbe14f
TG
277 if (err < 0)
278 return err;
279 }
280 return 0;
281 }
282
283 child = node_lookup_f(hwdb, node, search[i]);
284 node = child;
285 i++;
286 }
287 return 0;
288}
289
290static const char hwdb_bin_paths[] =
52efd56a
LP
291 "/etc/systemd/hwdb/hwdb.bin\0"
292 "/etc/udev/hwdb.bin\0"
293 "/usr/lib/systemd/hwdb/hwdb.bin\0"
23fbe14f 294#ifdef HAVE_SPLIT_USR
52efd56a 295 "/lib/systemd/hwdb/hwdb.bin\0"
23fbe14f 296#endif
52efd56a 297 UDEVLIBEXECDIR "/hwdb.bin\0";
23fbe14f
TG
298
299_public_ int sd_hwdb_new(sd_hwdb **ret) {
4afd3348 300 _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
23fbe14f
TG
301 const char *hwdb_bin_path;
302 const char sig[] = HWDB_SIG;
303
304 assert_return(ret, -EINVAL);
305
306 hwdb = new0(sd_hwdb, 1);
307 if (!hwdb)
308 return -ENOMEM;
309
310 hwdb->n_ref = REFCNT_INIT;
311
312 /* find hwdb.bin in hwdb_bin_paths */
313 NULSTR_FOREACH(hwdb_bin_path, hwdb_bin_paths) {
314 hwdb->f = fopen(hwdb_bin_path, "re");
315 if (hwdb->f)
316 break;
317 else if (errno == ENOENT)
318 continue;
319 else
320 return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path);
321 }
322
323 if (!hwdb->f) {
331d6a20 324 log_debug("hwdb.bin does not exist, please run systemd-hwdb update");
23fbe14f
TG
325 return -ENOENT;
326 }
327
328 if (fstat(fileno(hwdb->f), &hwdb->st) < 0 ||
329 (size_t)hwdb->st.st_size < offsetof(struct trie_header_f, strings_len) + 8)
330 return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path);
331
332 hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0);
333 if (hwdb->map == MAP_FAILED)
334 return log_debug_errno(errno, "error mapping %s: %m", hwdb_bin_path);
335
336 if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 ||
337 (size_t)hwdb->st.st_size != le64toh(hwdb->head->file_size)) {
338 log_debug("error recognizing the format of %s", hwdb_bin_path);
7e518afa 339 return -EINVAL;
23fbe14f
TG
340 }
341
342 log_debug("=== trie on-disk ===");
343 log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version));
1fa2f38f 344 log_debug("file size: %8"PRIi64" bytes", hwdb->st.st_size);
23fbe14f
TG
345 log_debug("header size %8"PRIu64" bytes", le64toh(hwdb->head->header_size));
346 log_debug("strings %8"PRIu64" bytes", le64toh(hwdb->head->strings_len));
347 log_debug("nodes %8"PRIu64" bytes", le64toh(hwdb->head->nodes_len));
348
349 *ret = hwdb;
350 hwdb = NULL;
351
352 return 0;
353}
354
355_public_ sd_hwdb *sd_hwdb_ref(sd_hwdb *hwdb) {
356 assert_return(hwdb, NULL);
357
358 assert_se(REFCNT_INC(hwdb->n_ref) >= 2);
359
360 return hwdb;
361}
362
363_public_ sd_hwdb *sd_hwdb_unref(sd_hwdb *hwdb) {
f0c4b1c3 364 if (hwdb && REFCNT_DEC(hwdb->n_ref) == 0) {
23fbe14f
TG
365 if (hwdb->map)
366 munmap((void *)hwdb->map, hwdb->st.st_size);
74ca738f 367 safe_fclose(hwdb->f);
23fbe14f
TG
368 free(hwdb->modalias);
369 ordered_hashmap_free(hwdb->properties);
370 free(hwdb);
371 }
372
373 return NULL;
374}
375
376bool hwdb_validate(sd_hwdb *hwdb) {
377 bool found = false;
378 const char* p;
379 struct stat st;
380
381 if (!hwdb)
382 return false;
383 if (!hwdb->f)
384 return false;
385
386 /* if hwdb.bin doesn't exist anywhere, we need to update */
387 NULSTR_FOREACH(p, hwdb_bin_paths) {
388 if (stat(p, &st) >= 0) {
389 found = true;
390 break;
391 }
392 }
393 if (!found)
394 return true;
395
396 if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim))
397 return true;
398 return false;
399}
400
401static int properties_prepare(sd_hwdb *hwdb, const char *modalias) {
402 _cleanup_free_ char *mod = NULL;
403 int r;
404
405 assert(hwdb);
406 assert(modalias);
407
408 if (streq_ptr(modalias, hwdb->modalias))
409 return 0;
410
411 mod = strdup(modalias);
412 if (!mod)
413 return -ENOMEM;
414
415 ordered_hashmap_clear(hwdb->properties);
416
417 hwdb->properties_modified = true;
418
419 r = trie_search_f(hwdb, modalias);
420 if (r < 0)
421 return r;
422
423 free(hwdb->modalias);
424 hwdb->modalias = mod;
425 mod = NULL;
426
427 return 0;
428}
429
430_public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **_value) {
3a04b789 431 const struct trie_value_entry_f *entry;
23fbe14f
TG
432 int r;
433
434 assert_return(hwdb, -EINVAL);
435 assert_return(hwdb->f, -EINVAL);
436 assert_return(modalias, -EINVAL);
437 assert_return(_value, -EINVAL);
438
439 r = properties_prepare(hwdb, modalias);
440 if (r < 0)
441 return r;
442
3a04b789
DH
443 entry = ordered_hashmap_get(hwdb->properties, key);
444 if (!entry)
23fbe14f
TG
445 return -ENOENT;
446
3a04b789 447 *_value = trie_string(hwdb, entry->value_off);
23fbe14f
TG
448
449 return 0;
450}
451
452_public_ int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias) {
453 int r;
454
455 assert_return(hwdb, -EINVAL);
456 assert_return(hwdb->f, -EINVAL);
457 assert_return(modalias, -EINVAL);
458
459 r = properties_prepare(hwdb, modalias);
460 if (r < 0)
461 return r;
462
463 hwdb->properties_modified = false;
464 hwdb->properties_iterator = ITERATOR_FIRST;
465
466 return 0;
467}
468
469_public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value) {
3a04b789 470 const struct trie_value_entry_f *entry;
8927b1da 471 const void *k;
23fbe14f
TG
472
473 assert_return(hwdb, -EINVAL);
474 assert_return(key, -EINVAL);
475 assert_return(value, -EINVAL);
476
477 if (hwdb->properties_modified)
478 return -EAGAIN;
479
3a04b789 480 ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, (void **)&entry, &k);
23fbe14f
TG
481 if (!k)
482 return 0;
483
484 *key = k;
3a04b789 485 *value = trie_string(hwdb, entry->value_off);
23fbe14f
TG
486
487 return 1;
488}