1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <ctype.h>
4 #include <stdio.h>
5 #include <sys/stat.h>
6 
7 #include "alloc-util.h"
8 #include "conf-files.h"
9 #include "fd-util.h"
10 #include "fileio.h"
11 #include "fs-util.h"
12 #include "hwdb-internal.h"
13 #include "hwdb-util.h"
14 #include "label.h"
15 #include "mkdir-label.h"
16 #include "nulstr-util.h"
17 #include "path-util.h"
18 #include "sort-util.h"
19 #include "strbuf.h"
20 #include "string-util.h"
21 #include "strv.h"
22 #include "tmpfile-util.h"
23 
24 static const char *default_hwdb_bin_dir = "/etc/udev";
25 static const char * const conf_file_dirs[] = {
26         "/etc/udev/hwdb.d",
27         UDEVLIBEXECDIR "/hwdb.d",
28         NULL
29 };
30 
31 /*
32  * Generic udev properties, key-value database based on modalias strings.
33  * Uses a Patricia/radix trie to index all matches for efficient lookup.
34  */
35 
36 /* in-memory trie objects */
37 struct trie {
38         struct trie_node *root;
39         struct strbuf *strings;
40 
41         size_t nodes_count;
42         size_t children_count;
43         size_t values_count;
44 };
45 
46 struct trie_node {
47         /* prefix, common part for all children of this node */
48         size_t prefix_off;
49 
50         /* sorted array of pointers to children nodes */
51         struct trie_child_entry *children;
52         uint8_t children_count;
53 
54         /* sorted array of key-value pairs */
55         struct trie_value_entry *values;
56         size_t values_count;
57 };
58 
59 /* children array item with char (0-255) index */
60 struct trie_child_entry {
61         uint8_t c;
62         struct trie_node *child;
63 };
64 
65 /* value array item with key-value pairs */
66 struct trie_value_entry {
67         size_t key_off;
68         size_t value_off;
69         size_t filename_off;
70         uint32_t line_number;
71         uint16_t file_priority;
72 };
73 
trie_children_cmp(const struct trie_child_entry * a,const struct trie_child_entry * b)74 static int trie_children_cmp(const struct trie_child_entry *a, const struct trie_child_entry *b) {
75         return CMP(a->c, b->c);
76 }
77 
node_add_child(struct trie * trie,struct trie_node * node,struct trie_node * node_child,uint8_t c)78 static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) {
79         struct trie_child_entry *child;
80 
81         /* extend array, add new entry, sort for bisection */
82         child = reallocarray(node->children, node->children_count + 1, sizeof(struct trie_child_entry));
83         if (!child)
84                 return -ENOMEM;
85 
86         node->children = child;
87         trie->children_count++;
88         node->children[node->children_count].c = c;
89         node->children[node->children_count].child = node_child;
90         node->children_count++;
91         typesafe_qsort(node->children, node->children_count, trie_children_cmp);
92         trie->nodes_count++;
93 
94         return 0;
95 }
96 
node_lookup(const struct trie_node * node,uint8_t c)97 static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) {
98         struct trie_child_entry *child;
99         struct trie_child_entry search;
100 
101         search.c = c;
102         child = typesafe_bsearch(&search, node->children, node->children_count, trie_children_cmp);
103         if (child)
104                 return child->child;
105         return NULL;
106 }
107 
trie_node_cleanup(struct trie_node * node)108 static void trie_node_cleanup(struct trie_node *node) {
109         if (!node)
110                 return;
111 
112         for (size_t i = 0; i < node->children_count; i++)
113                 trie_node_cleanup(node->children[i].child);
114         free(node->children);
115         free(node->values);
116         free(node);
117 }
118 
trie_free(struct trie * trie)119 static struct trie* trie_free(struct trie *trie) {
120         if (!trie)
121                 return NULL;
122 
123         trie_node_cleanup(trie->root);
124         strbuf_free(trie->strings);
125         return mfree(trie);
126 }
127 
128 DEFINE_TRIVIAL_CLEANUP_FUNC(struct trie*, trie_free);
129 
trie_values_cmp(const struct trie_value_entry * a,const struct trie_value_entry * b,struct trie * trie)130 static int trie_values_cmp(const struct trie_value_entry *a, const struct trie_value_entry *b, struct trie *trie) {
131         return strcmp(trie->strings->buf + a->key_off,
132                       trie->strings->buf + b->key_off);
133 }
134 
trie_node_add_value(struct trie * trie,struct trie_node * node,const char * key,const char * value,const char * filename,uint16_t file_priority,uint32_t line_number,bool compat)135 static int trie_node_add_value(struct trie *trie, struct trie_node *node,
136                                const char *key, const char *value,
137                                const char *filename, uint16_t file_priority, uint32_t line_number, bool compat) {
138         ssize_t k, v, fn = 0;
139         struct trie_value_entry *val;
140 
141         k = strbuf_add_string(trie->strings, key, strlen(key));
142         if (k < 0)
143                 return k;
144         v = strbuf_add_string(trie->strings, value, strlen(value));
145         if (v < 0)
146                 return v;
147 
148         if (!compat) {
149                 fn = strbuf_add_string(trie->strings, filename, strlen(filename));
150                 if (fn < 0)
151                         return fn;
152         }
153 
154         if (node->values_count) {
155                 struct trie_value_entry search = {
156                         .key_off = k,
157                         .value_off = v,
158                 };
159 
160                 val = typesafe_bsearch_r(&search, node->values, node->values_count, trie_values_cmp, trie);
161                 if (val) {
162                         /* At this point we have 2 identical properties on the same match-string.
163                          * Since we process files in order, we just replace the previous value. */
164                         val->value_off = v;
165                         val->filename_off = fn;
166                         val->file_priority = file_priority;
167                         val->line_number = line_number;
168                         return 0;
169                 }
170         }
171 
172         /* extend array, add new entry, sort for bisection */
173         val = reallocarray(node->values, node->values_count + 1, sizeof(struct trie_value_entry));
174         if (!val)
175                 return -ENOMEM;
176         trie->values_count++;
177         node->values = val;
178         node->values[node->values_count] = (struct trie_value_entry) {
179                 .key_off = k,
180                 .value_off = v,
181                 .filename_off = fn,
182                 .file_priority = file_priority,
183                 .line_number = line_number,
184         };
185         node->values_count++;
186         typesafe_qsort_r(node->values, node->values_count, trie_values_cmp, trie);
187         return 0;
188 }
189 
trie_insert(struct trie * trie,struct trie_node * node,const char * search,const char * key,const char * value,const char * filename,uint16_t file_priority,uint32_t line_number,bool compat)190 static int trie_insert(struct trie *trie, struct trie_node *node, const char *search,
191                        const char *key, const char *value,
192                        const char *filename, uint16_t file_priority, uint32_t line_number, bool compat) {
193         int r = 0;
194 
195         for (size_t i = 0;; i++) {
196                 size_t p;
197                 uint8_t c;
198                 struct trie_node *child;
199 
200                 for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) {
201                         _cleanup_free_ struct trie_node *new_child = NULL;
202                         _cleanup_free_ char *s = NULL;
203                         ssize_t off;
204 
205                         if (c == search[i + p])
206                                 continue;
207 
208                         /* split node */
209                         new_child = new(struct trie_node, 1);
210                         if (!new_child)
211                                 return -ENOMEM;
212 
213                         /* move values from parent to child */
214                         *new_child = (struct trie_node) {
215                                 .prefix_off = node->prefix_off + p+1,
216                                 .children = node->children,
217                                 .children_count = node->children_count,
218                                 .values = node->values,
219                                 .values_count = node->values_count,
220                         };
221 
222                         /* update parent; use strdup() because the source gets realloc()d */
223                         s = strndup(trie->strings->buf + node->prefix_off, p);
224                         if (!s)
225                                 return -ENOMEM;
226 
227                         off = strbuf_add_string(trie->strings, s, p);
228                         if (off < 0)
229                                 return off;
230 
231                         *node = (struct trie_node) {
232                                 .prefix_off = off,
233                         };
234                         r = node_add_child(trie, node, new_child, c);
235                         if (r < 0)
236                                 return r;
237 
238                         new_child = NULL; /* avoid cleanup */
239                         break;
240                 }
241                 i += p;
242 
243                 c = search[i];
244                 if (c == '\0')
245                         return trie_node_add_value(trie, node, key, value, filename, file_priority, line_number, compat);
246 
247                 child = node_lookup(node, c);
248                 if (!child) {
249                         _cleanup_free_ struct trie_node *new_child = NULL;
250                         ssize_t off;
251 
252                         /* new child */
253                         new_child = new(struct trie_node, 1);
254                         if (!new_child)
255                                 return -ENOMEM;
256 
257                         off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1));
258                         if (off < 0)
259                                 return off;
260 
261                         *new_child = (struct trie_node) {
262                                 .prefix_off = off,
263                         };
264 
265                         r = node_add_child(trie, node, new_child, c);
266                         if (r < 0)
267                                 return r;
268 
269                         child = TAKE_PTR(new_child);
270                         return trie_node_add_value(trie, child, key, value, filename, file_priority, line_number, compat);
271                 }
272 
273                 node = child;
274         }
275 }
276 
277 struct trie_f {
278         FILE *f;
279         struct trie *trie;
280         uint64_t strings_off;
281 
282         uint64_t nodes_count;
283         uint64_t children_count;
284         uint64_t values_count;
285 };
286 
287 /* calculate the storage space for the nodes, children arrays, value arrays */
trie_store_nodes_size(struct trie_f * trie,struct trie_node * node,bool compat)288 static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node, bool compat) {
289         for (uint64_t i = 0; i < node->children_count; i++)
290                 trie_store_nodes_size(trie, node->children[i].child, compat);
291 
292         trie->strings_off += sizeof(struct trie_node_f);
293         for (uint64_t i = 0; i < node->children_count; i++)
294                 trie->strings_off += sizeof(struct trie_child_entry_f);
295         for (uint64_t i = 0; i < node->values_count; i++)
296                 trie->strings_off += compat ? sizeof(struct trie_value_entry_f) : sizeof(struct trie_value_entry2_f);
297 }
298 
trie_store_nodes(struct trie_f * trie,struct trie_node * node,bool compat)299 static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node, bool compat) {
300         struct trie_node_f n = {
301                 .prefix_off = htole64(trie->strings_off + node->prefix_off),
302                 .children_count = node->children_count,
303                 .values_count = htole64(node->values_count),
304         };
305         _cleanup_free_ struct trie_child_entry_f *children = NULL;
306         int64_t node_off;
307 
308         if (node->children_count) {
309                 children = new(struct trie_child_entry_f, node->children_count);
310                 if (!children)
311                         return -ENOMEM;
312         }
313 
314         /* post-order recursion */
315         for (uint64_t i = 0; i < node->children_count; i++) {
316                 int64_t child_off;
317 
318                 child_off = trie_store_nodes(trie, node->children[i].child, compat);
319                 if (child_off < 0)
320                         return child_off;
321 
322                 children[i] = (struct trie_child_entry_f) {
323                         .c = node->children[i].c,
324                         .child_off = htole64(child_off),
325                 };
326         }
327 
328         /* write node */
329         node_off = ftello(trie->f);
330         fwrite(&n, sizeof(struct trie_node_f), 1, trie->f);
331         trie->nodes_count++;
332 
333         /* append children array */
334         if (node->children_count) {
335                 fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f);
336                 trie->children_count += node->children_count;
337         }
338 
339         /* append values array */
340         for (uint64_t i = 0; i < node->values_count; i++) {
341                 struct trie_value_entry2_f v = {
342                         .key_off = htole64(trie->strings_off + node->values[i].key_off),
343                         .value_off = htole64(trie->strings_off + node->values[i].value_off),
344                         .filename_off = htole64(trie->strings_off + node->values[i].filename_off),
345                         .line_number = htole32(node->values[i].line_number),
346                         .file_priority = htole16(node->values[i].file_priority),
347                 };
348 
349                 fwrite(&v, compat ? sizeof(struct trie_value_entry_f) : sizeof(struct trie_value_entry2_f), 1, trie->f);
350         }
351         trie->values_count += node->values_count;
352 
353         return node_off;
354 }
355 
trie_store(struct trie * trie,const char * filename,bool compat)356 static int trie_store(struct trie *trie, const char *filename, bool compat) {
357         struct trie_f t = {
358                 .trie = trie,
359         };
360         _cleanup_free_ char *filename_tmp = NULL;
361         int64_t pos;
362         int64_t root_off;
363         int64_t size;
364         struct trie_header_f h = {
365                 .signature = HWDB_SIG,
366                 .tool_version = htole64(PROJECT_VERSION),
367                 .header_size = htole64(sizeof(struct trie_header_f)),
368                 .node_size = htole64(sizeof(struct trie_node_f)),
369                 .child_entry_size = htole64(sizeof(struct trie_child_entry_f)),
370                 .value_entry_size = htole64(compat ? sizeof(struct trie_value_entry_f) : sizeof(struct trie_value_entry2_f)),
371         };
372         int r;
373 
374         /* calculate size of header, nodes, children entries, value entries */
375         t.strings_off = sizeof(struct trie_header_f);
376         trie_store_nodes_size(&t, trie->root, compat);
377 
378         r = fopen_temporary(filename, &t.f, &filename_tmp);
379         if (r < 0)
380                 return r;
381         (void) fchmod(fileno(t.f), 0444);
382 
383         /* write nodes */
384         if (fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET) < 0)
385                 goto error_fclose;
386 
387         root_off = trie_store_nodes(&t, trie->root, compat);
388         h.nodes_root_off = htole64(root_off);
389         pos = ftello(t.f);
390         h.nodes_len = htole64(pos - sizeof(struct trie_header_f));
391 
392         /* write string buffer */
393         fwrite(trie->strings->buf, trie->strings->len, 1, t.f);
394         h.strings_len = htole64(trie->strings->len);
395 
396         /* write header */
397         size = ftello(t.f);
398         h.file_size = htole64(size);
399         if (fseeko(t.f, 0, SEEK_SET) < 0)
400                 goto error_fclose;
401         fwrite(&h, sizeof(struct trie_header_f), 1, t.f);
402 
403         if (ferror(t.f))
404                 goto error_fclose;
405         if (fflush(t.f) < 0)
406                 goto error_fclose;
407         if (fsync(fileno(t.f)) < 0)
408                 goto error_fclose;
409         if (rename(filename_tmp, filename) < 0)
410                 goto error_fclose;
411 
412         /* write succeeded */
413         fclose(t.f);
414 
415         log_debug("=== trie on-disk ===");
416         log_debug("size:             %8"PRIi64" bytes", size);
417         log_debug("header:           %8zu bytes", sizeof(struct trie_header_f));
418         log_debug("nodes:            %8"PRIu64" bytes (%8"PRIu64")",
419                   t.nodes_count * sizeof(struct trie_node_f), t.nodes_count);
420         log_debug("child pointers:   %8"PRIu64" bytes (%8"PRIu64")",
421                   t.children_count * sizeof(struct trie_child_entry_f), t.children_count);
422         log_debug("value pointers:   %8"PRIu64" bytes (%8"PRIu64")",
423                   t.values_count * (compat ? sizeof(struct trie_value_entry_f) : sizeof(struct trie_value_entry2_f)), t.values_count);
424         log_debug("string store:     %8zu bytes", trie->strings->len);
425         log_debug("strings start:    %8"PRIu64, t.strings_off);
426         return 0;
427 
428  error_fclose:
429         r = -errno;
430         fclose(t.f);
431         (void) unlink(filename_tmp);
432         return r;
433 }
434 
insert_data(struct trie * trie,char ** match_list,char * line,const char * filename,uint16_t file_priority,uint32_t line_number,bool compat)435 static int insert_data(struct trie *trie, char **match_list, char *line, const char *filename,
436                        uint16_t file_priority, uint32_t line_number, bool compat) {
437         char *value;
438 
439         assert(line[0] == ' ');
440 
441         value = strchr(line, '=');
442         if (!value)
443                 return log_syntax(NULL, LOG_WARNING, filename, line_number, SYNTHETIC_ERRNO(EINVAL),
444                                   "Key-value pair expected but got \"%s\", ignoring.", line);
445 
446         value[0] = '\0';
447         value++;
448 
449         /* Replace multiple leading spaces by a single space */
450         while (isblank(line[0]) && isblank(line[1]))
451                 line++;
452 
453         if (isempty(line + 1))
454                 return log_syntax(NULL, LOG_WARNING, filename, line_number, SYNTHETIC_ERRNO(EINVAL),
455                                   "Empty key in \"%s=%s\", ignoring.",
456                                   line, value);
457 
458         STRV_FOREACH(entry, match_list)
459                 trie_insert(trie, trie->root, *entry, line, value, filename, file_priority, line_number, compat);
460 
461         return 0;
462 }
463 
import_file(struct trie * trie,const char * filename,uint16_t file_priority,bool compat)464 static int import_file(struct trie *trie, const char *filename, uint16_t file_priority, bool compat) {
465         enum {
466                 HW_NONE,
467                 HW_MATCH,
468                 HW_DATA,
469         } state = HW_NONE;
470         _cleanup_fclose_ FILE *f = NULL;
471         _cleanup_strv_free_ char **match_list = NULL;
472         uint32_t line_number = 0;
473         int r, err;
474 
475         f = fopen(filename, "re");
476         if (!f)
477                 return -errno;
478 
479         for (;;) {
480                 _cleanup_free_ char *line = NULL;
481                 size_t len;
482                 char *pos;
483 
484                 r = read_line_full(f, LONG_LINE_MAX, READ_LINE_NOT_A_TTY, &line);
485                 if (r < 0)
486                         return r;
487                 if (r == 0)
488                         break;
489 
490                 line_number ++;
491 
492                 /* comment line */
493                 if (line[0] == '#')
494                         continue;
495 
496                 /* strip trailing comment */
497                 pos = strchr(line, '#');
498                 if (pos)
499                         pos[0] = '\0';
500 
501                 /* strip trailing whitespace */
502                 len = strlen(line);
503                 while (len > 0 && isspace(line[len-1]))
504                         len--;
505                 line[len] = '\0';
506 
507                 switch (state) {
508                 case HW_NONE:
509                         if (len == 0)
510                                 break;
511 
512                         if (line[0] == ' ') {
513                                 r = log_syntax(NULL, LOG_WARNING, filename, line_number, SYNTHETIC_ERRNO(EINVAL),
514                                                "Match expected but got indented property \"%s\", ignoring line.", line);
515                                 break;
516                         }
517 
518                         /* start of record, first match */
519                         state = HW_MATCH;
520 
521                         err = strv_extend(&match_list, line);
522                         if (err < 0)
523                                 return err;
524 
525                         break;
526 
527                 case HW_MATCH:
528                         if (len == 0) {
529                                 r = log_syntax(NULL, LOG_WARNING, filename, line_number, SYNTHETIC_ERRNO(EINVAL),
530                                                "Property expected, ignoring record with no properties.");
531                                 state = HW_NONE;
532                                 match_list = strv_free(match_list);
533                                 break;
534                         }
535 
536                         if (line[0] != ' ') {
537                                 /* another match */
538                                 err = strv_extend(&match_list, line);
539                                 if (err < 0)
540                                         return err;
541 
542                                 break;
543                         }
544 
545                         /* first data */
546                         state = HW_DATA;
547                         err = insert_data(trie, match_list, line, filename, file_priority, line_number, compat);
548                         if (err < 0)
549                                 r = err;
550                         break;
551 
552                 case HW_DATA:
553                         if (len == 0) {
554                                 /* end of record */
555                                 state = HW_NONE;
556                                 match_list = strv_free(match_list);
557                                 break;
558                         }
559 
560                         if (line[0] != ' ') {
561                                 r = log_syntax(NULL, LOG_WARNING, filename, line_number, SYNTHETIC_ERRNO(EINVAL),
562                                                "Property or empty line expected, got \"%s\", ignoring record.", line);
563                                 state = HW_NONE;
564                                 match_list = strv_free(match_list);
565                                 break;
566                         }
567 
568                         err = insert_data(trie, match_list, line, filename, file_priority, line_number, compat);
569                         if (err < 0)
570                                 r = err;
571                         break;
572                 };
573         }
574 
575         if (state == HW_MATCH)
576                 log_syntax(NULL, LOG_WARNING, filename, line_number, 0,
577                            "Property expected, ignoring record with no properties.");
578 
579         return r;
580 }
581 
hwdb_update(const char * root,const char * hwdb_bin_dir,bool strict,bool compat)582 int hwdb_update(const char *root, const char *hwdb_bin_dir, bool strict, bool compat) {
583         _cleanup_free_ char *hwdb_bin = NULL;
584         _cleanup_(trie_freep) struct trie *trie = NULL;
585         _cleanup_strv_free_ char **files = NULL;
586         uint16_t file_priority = 1;
587         int r = 0, err;
588 
589         /* The argument 'compat' controls the format version of database. If false, then hwdb.bin will be
590          * created with additional information such that priority, line number, and filename of database
591          * source. If true, then hwdb.bin will be created without the information. systemd-hwdb command
592          * should set the argument false, and 'udevadm hwdb' command should set it true. */
593 
594         trie = new0(struct trie, 1);
595         if (!trie)
596                 return -ENOMEM;
597 
598         /* string store */
599         trie->strings = strbuf_new();
600         if (!trie->strings)
601                 return -ENOMEM;
602 
603         /* index */
604         trie->root = new0(struct trie_node, 1);
605         if (!trie->root)
606                 return -ENOMEM;
607 
608         trie->nodes_count++;
609 
610         err = conf_files_list_strv(&files, ".hwdb", root, 0, conf_file_dirs);
611         if (err < 0)
612                 return log_error_errno(err, "Failed to enumerate hwdb files: %m");
613 
614         STRV_FOREACH(f, files) {
615                 log_debug("Reading file \"%s\"", *f);
616                 err = import_file(trie, *f, file_priority++, compat);
617                 if (err < 0 && strict)
618                         r = err;
619         }
620 
621         strbuf_complete(trie->strings);
622 
623         log_debug("=== trie in-memory ===");
624         log_debug("nodes:            %8zu bytes (%8zu)",
625                   trie->nodes_count * sizeof(struct trie_node), trie->nodes_count);
626         log_debug("children arrays:  %8zu bytes (%8zu)",
627                   trie->children_count * sizeof(struct trie_child_entry), trie->children_count);
628         log_debug("values arrays:    %8zu bytes (%8zu)",
629                   trie->values_count * sizeof(struct trie_value_entry), trie->values_count);
630         log_debug("strings:          %8zu bytes",
631                   trie->strings->len);
632         log_debug("strings incoming: %8zu bytes (%8zu)",
633                   trie->strings->in_len, trie->strings->in_count);
634         log_debug("strings dedup'ed: %8zu bytes (%8zu)",
635                   trie->strings->dedup_len, trie->strings->dedup_count);
636 
637         hwdb_bin = path_join(root, hwdb_bin_dir ?: default_hwdb_bin_dir, "hwdb.bin");
638         if (!hwdb_bin)
639                 return -ENOMEM;
640 
641         (void) mkdir_parents_label(hwdb_bin, 0755);
642         err = trie_store(trie, hwdb_bin, compat);
643         if (err < 0)
644                 return log_error_errno(err, "Failed to write database %s: %m", hwdb_bin);
645 
646         err = label_fix(hwdb_bin, 0);
647         if (err < 0)
648                 return err;
649 
650         return r;
651 }
652 
hwdb_query(const char * modalias)653 int hwdb_query(const char *modalias) {
654         _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
655         const char *key, *value;
656         int r;
657 
658         assert(modalias);
659 
660         r = sd_hwdb_new(&hwdb);
661         if (r < 0)
662                 return r;
663 
664         SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value)
665                 printf("%s=%s\n", key, value);
666 
667         return 0;
668 }
669 
hwdb_validate(sd_hwdb * hwdb)670 bool hwdb_validate(sd_hwdb *hwdb) {
671         bool found = false;
672         const char* p;
673         struct stat st;
674 
675         if (!hwdb)
676                 return false;
677         if (!hwdb->f)
678                 return false;
679 
680         /* if hwdb.bin doesn't exist anywhere, we need to update */
681         NULSTR_FOREACH(p, hwdb_bin_paths)
682                 if (stat(p, &st) >= 0) {
683                         found = true;
684                         break;
685                 }
686         if (!found)
687                 return true;
688 
689         if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim))
690                 return true;
691         return false;
692 }
693