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