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