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