1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2017 Netflix, Inc.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 #include <sys/param.h>
29 #include <ctype.h>
30 #include <devinfo.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <getopt.h>
35 #include <stdbool.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <sysexits.h>
40 #include <unistd.h>
41 #include <sys/linker.h>
42 #include <sys/module.h>
43 #include <sys/stat.h>
44 #include <sys/sysctl.h>
45
46 /* options descriptor */
47 static struct option longopts[] = {
48 { "all", no_argument, NULL, 'a' },
49 { "dump", no_argument, NULL, 'd' },
50 { "hints", required_argument, NULL, 'h' },
51 { "nomatch", required_argument, NULL, 'p' },
52 { "quiet", no_argument, NULL, 'q' },
53 { "unbound", no_argument, NULL, 'u' },
54 { "verbose", no_argument, NULL, 'v' },
55 { NULL, 0, NULL, 0 }
56 };
57
58 #define DEVMATCH_MAX_HITS 256
59
60 static bool all_flag;
61 static bool dump_flag;
62 static char *linker_hints;
63 static char *nomatch_str;
64 static bool quiet_flag;
65 static bool unbound_flag;
66 static bool verbose_flag;
67
68 static void *hints;
69 static void *hints_end;
70 static struct devinfo_dev *root;
71
72 static void *
read_hints(const char * fn,size_t * len)73 read_hints(const char *fn, size_t *len)
74 {
75 void *h;
76 int fd;
77 struct stat sb;
78
79 fd = open(fn, O_RDONLY);
80 if (fd < 0) {
81 if (errno == ENOENT)
82 return NULL;
83 err(1, "Can't open %s for reading", fn);
84 }
85 if (fstat(fd, &sb) != 0)
86 err(1, "Can't fstat %s\n", fn);
87 h = malloc(sb.st_size);
88 if (h == NULL)
89 err(1, "not enough space to read hints file of %ju bytes", (uintmax_t)sb.st_size);
90 if (read(fd, h, sb.st_size) != sb.st_size)
91 err(1, "Can't read in %ju bytes from %s", (uintmax_t)sb.st_size, fn);
92 close(fd);
93 *len = sb.st_size;
94 return h;
95 }
96
97 static void
read_linker_hints(void)98 read_linker_hints(void)
99 {
100 char fn[MAXPATHLEN];
101 char *modpath, *p, *q;
102 size_t buflen, len;
103
104 if (linker_hints == NULL) {
105 void *all_hints = NULL;
106 size_t all_len = 0;
107
108 if (sysctlbyname("kern.module_path", NULL, &buflen, NULL, 0) < 0)
109 errx(1, "Can't find kernel module path.");
110 modpath = malloc(buflen);
111 if (modpath == NULL)
112 err(1, "Can't get memory for modpath.");
113 if (sysctlbyname("kern.module_path", modpath, &buflen, NULL, 0) < 0)
114 errx(1, "Can't find kernel module path.");
115 p = modpath;
116 while ((q = strsep(&p, ";")) != NULL) {
117 void *h;
118
119 snprintf(fn, sizeof(fn), "%s/linker.hints", q);
120 h = read_hints(fn, &len);
121 if (h == NULL)
122 continue;
123 if (len < sizeof(int) ||
124 *(int *)(intptr_t)h != LINKER_HINTS_VERSION) {
125 free(h);
126 continue;
127 }
128 if (all_hints == NULL) {
129 all_hints = h;
130 all_len = len;
131 } else {
132 void *merged;
133
134 merged = realloc(all_hints, all_len + len - sizeof(int));
135 if (merged == NULL) {
136 free(h);
137 continue;
138 }
139 all_hints = merged;
140 memcpy((char *)all_hints + all_len,
141 (char *)h + sizeof(int),
142 len - sizeof(int));
143 all_len += len - sizeof(int);
144 free(h);
145 }
146 }
147 hints = all_hints;
148 len = all_len;
149 if (hints == NULL) {
150 if (quiet_flag)
151 exit(EX_UNAVAILABLE);
152 else
153 errx(EX_UNAVAILABLE, "Can't read linker hints file.");
154 }
155 } else {
156 hints = read_hints(linker_hints, &len);
157 if (hints == NULL)
158 err(1, "Can't open %s for reading", fn);
159 }
160
161 if (len < sizeof(int)) {
162 warnx("Linker hints file too short.");
163 free(hints);
164 hints = NULL;
165 return;
166 }
167 if (*(int *)(intptr_t)hints != LINKER_HINTS_VERSION) {
168 warnx("Linker hints version %d doesn't match expected %d.",
169 *(int *)(intptr_t)hints, LINKER_HINTS_VERSION);
170 free(hints);
171 hints = NULL;
172 }
173 if (hints != NULL)
174 hints_end = (void *)((intptr_t)hints + (intptr_t)len);
175 }
176
177 static int
getint(void ** ptr)178 getint(void **ptr)
179 {
180 int *p = *ptr;
181 int rv;
182
183 p = (int *)roundup2((intptr_t)p, sizeof(int));
184 rv = *p++;
185 *ptr = p;
186 return rv;
187 }
188
189 static void
getstr(void ** ptr,char * val)190 getstr(void **ptr, char *val)
191 {
192 int *p = *ptr;
193 char *c = (char *)p;
194 int len = *(uint8_t *)c;
195
196 memcpy(val, c + 1, len);
197 val[len] = 0;
198 c += len + 1;
199 *ptr = (void *)c;
200 }
201
202 static int
pnpval_as_int(const char * val,const char * pnpinfo)203 pnpval_as_int(const char *val, const char *pnpinfo)
204 {
205 int rv;
206 char key[256];
207 char *cp;
208
209 if (pnpinfo == NULL)
210 return -1;
211
212 cp = strchr(val, ';');
213 key[0] = ' ';
214 if (cp == NULL)
215 strlcpy(key + 1, val, sizeof(key) - 1);
216 else {
217 memcpy(key + 1, val, cp - val);
218 key[cp - val + 1] = '\0';
219 }
220 strlcat(key, "=", sizeof(key));
221 if (strncmp(key + 1, pnpinfo, strlen(key + 1)) == 0)
222 rv = strtol(pnpinfo + strlen(key + 1), NULL, 0);
223 else {
224 cp = strstr(pnpinfo, key);
225 if (cp == NULL)
226 rv = -1;
227 else
228 rv = strtol(cp + strlen(key), NULL, 0);
229 }
230 return rv;
231 }
232
233 static void
quoted_strcpy(char * dst,const char * src)234 quoted_strcpy(char *dst, const char *src)
235 {
236 char q = ' ';
237
238 if (*src == '\'' || *src == '"')
239 q = *src++;
240 while (*src && *src != q)
241 *dst++ = *src++; // XXX backtick quoting
242 *dst++ = '\0';
243 // XXX overflow
244 }
245
246 static char *
pnpval_as_str(const char * val,const char * pnpinfo)247 pnpval_as_str(const char *val, const char *pnpinfo)
248 {
249 static char retval[256];
250 char key[256];
251 char *cp;
252
253 if (pnpinfo == NULL) {
254 *retval = '\0';
255 return retval;
256 }
257
258 cp = strchr(val, ';');
259 key[0] = ' ';
260 if (cp == NULL)
261 strlcpy(key + 1, val, sizeof(key) - 1);
262 else {
263 memcpy(key + 1, val, cp - val);
264 key[cp - val + 1] = '\0';
265 }
266 strlcat(key, "=", sizeof(key));
267 if (strncmp(key + 1, pnpinfo, strlen(key + 1)) == 0)
268 quoted_strcpy(retval, pnpinfo + strlen(key + 1));
269 else {
270 cp = strstr(pnpinfo, key);
271 if (cp == NULL)
272 strcpy(retval, "MISSING");
273 else
274 quoted_strcpy(retval, cp + strlen(key));
275 }
276 return retval;
277 }
278
279 static void
search_hints(const char * bus,const char * dev,const char * pnpinfo)280 search_hints(const char *bus, const char *dev, const char *pnpinfo)
281 {
282 char val1[256], val2[256];
283 int ival, len, ents, i, notme, mask, bit, v, found;
284 void *ptr, *walker;
285 char *lastmod = NULL, *cp, *s;
286
287 walker = hints;
288 getint(&walker);
289 found = 0;
290 if (verbose_flag)
291 printf("Searching bus %s dev %s for pnpinfo %s\n",
292 bus, dev, pnpinfo);
293 while (walker < hints_end) {
294 len = getint(&walker);
295 ival = getint(&walker);
296 ptr = walker;
297 switch (ival) {
298 case MDT_VERSION:
299 getstr(&ptr, val1);
300 ival = getint(&ptr);
301 getstr(&ptr, val2);
302 if (dump_flag || verbose_flag)
303 printf("Version: if %s.%d kmod %s\n", val1, ival, val2);
304 break;
305 case MDT_MODULE:
306 getstr(&ptr, val1);
307 getstr(&ptr, val2);
308 if (lastmod)
309 free(lastmod);
310 lastmod = strdup(val2);
311 if (dump_flag || verbose_flag)
312 printf("Module %s in %s\n", val1, val2);
313 break;
314 case MDT_PNP_INFO:
315 if (!dump_flag && !unbound_flag && lastmod && strcmp(lastmod, "kernel") == 0)
316 break;
317 getstr(&ptr, val1);
318 getstr(&ptr, val2);
319 ents = getint(&ptr);
320 if (dump_flag || verbose_flag)
321 printf("PNP info for bus %s format %s %d entries (%s)\n",
322 val1, val2, ents, lastmod);
323 if (strcmp(val1, "usb") == 0) {
324 if (verbose_flag)
325 printf("Treating usb as uhub -- bug in source table still?\n");
326 strcpy(val1, "uhub");
327 }
328 if (bus && strcmp(val1, bus) != 0) {
329 if (verbose_flag)
330 printf("Skipped because table for bus %s, looking for %s\n",
331 val1, bus);
332 break;
333 }
334 for (i = 0; i < ents; i++) {
335 if (verbose_flag)
336 printf("---------- Entry %d ----------\n", i);
337 if (dump_flag)
338 printf(" ");
339 cp = val2;
340 notme = 0;
341 mask = -1;
342 bit = -1;
343 do {
344 switch (*cp) {
345 /* All integer fields */
346 case 'I':
347 case 'J':
348 case 'G':
349 case 'L':
350 case 'M':
351 ival = getint(&ptr);
352 if (dump_flag) {
353 printf("%#x:", ival);
354 break;
355 }
356 if (bit >= 0 && ((1 << bit) & mask) == 0)
357 break;
358 if (cp[2] == '#') {
359 if (verbose_flag) {
360 printf("Ignoring %s (%c) table=%#x tomatch=%#x\n",
361 cp + 2, *cp, v, ival);
362 }
363 break;
364 }
365 v = pnpval_as_int(cp + 2, pnpinfo);
366 if (verbose_flag)
367 printf("Matching %s (%c) table=%#x tomatch=%#x\n",
368 cp + 2, *cp, v, ival);
369 switch (*cp) {
370 case 'J':
371 if (ival == -1)
372 break;
373 /*FALLTHROUGH*/
374 case 'I':
375 if (v != ival)
376 notme++;
377 break;
378 case 'G':
379 if (v < ival)
380 notme++;
381 break;
382 case 'L':
383 if (v > ival)
384 notme++;
385 break;
386 case 'M':
387 mask = ival;
388 break;
389 }
390 break;
391 /* String fields */
392 case 'D':
393 case 'Z':
394 getstr(&ptr, val1);
395 if (dump_flag) {
396 printf("'%s':", val1);
397 break;
398 }
399 if (*cp == 'D')
400 break;
401 if (bit >= 0 && ((1 << bit) & mask) == 0)
402 break;
403 if (cp[2] == '#') {
404 if (verbose_flag) {
405 printf("Ignoring %s (%c) table=%#x tomatch=%#x\n",
406 cp + 2, *cp, v, ival);
407 }
408 break;
409 }
410 s = pnpval_as_str(cp + 2, pnpinfo);
411 if (verbose_flag)
412 printf("Matching %s (%c) table=%s tomatch=%s\n",
413 cp + 2, *cp, s, val1);
414 if (strcmp(s, val1) != 0)
415 notme++;
416 break;
417 /* Key override fields, required to be last in the string */
418 case 'T':
419 /*
420 * This is imperfect and only does one key and will be redone
421 * to be more general for multiple keys. Currently, nothing
422 * does that.
423 */
424 if (dump_flag) /* No per-row data stored */
425 break;
426 if (cp[strlen(cp) - 1] == ';') /* Skip required ; at end */
427 cp[strlen(cp) - 1] = '\0'; /* in case it's not there */
428 if ((s = strstr(pnpinfo, cp + 2)) == NULL)
429 notme++;
430 else if (s > pnpinfo && s[-1] != ' ')
431 notme++;
432 break;
433 default:
434 fprintf(stderr, "Unknown field type %c\n:", *cp);
435 break;
436 }
437 bit++;
438 cp = strchr(cp, ';');
439 if (cp)
440 cp++;
441 } while (cp && *cp);
442 if (dump_flag)
443 printf("\n");
444 else if (!notme) {
445 if (!unbound_flag) {
446 if (all_flag)
447 printf("%s: %s\n", *dev ? dev : "unattached", lastmod);
448 else
449 printf("%s\n", lastmod);
450 if (verbose_flag)
451 printf("Matches --- %s ---\n", lastmod);
452 }
453 found++;
454 }
455 }
456 break;
457 default:
458 if (dump_flag)
459 printf("Unknown Type %d len %d\n", ival, len);
460 break;
461 }
462 walker = (void *)(len - sizeof(int) + (intptr_t)walker);
463 }
464 if (unbound_flag && found == 0 && *pnpinfo) {
465 if (verbose_flag)
466 printf("------------------------- ");
467 printf("%s on %s pnpinfo %s", *dev ? dev : "unattached", bus, pnpinfo);
468 if (verbose_flag)
469 printf(" -------------------------");
470 printf("\n");
471 }
472 free(lastmod);
473 }
474
475 static int
find_unmatched(struct devinfo_dev * dev,void * arg)476 find_unmatched(struct devinfo_dev *dev, void *arg)
477 {
478 struct devinfo_dev *parent;
479 char *bus, *p;
480
481 do {
482 if (!all_flag && dev->dd_name[0] != '\0')
483 break;
484 if (!(dev->dd_flags & DF_ENABLED))
485 break;
486 if (!all_flag && dev->dd_flags & DF_ATTACHED_ONCE)
487 break;
488 parent = devinfo_handle_to_device(dev->dd_parent);
489 bus = strdup(parent->dd_name);
490 p = bus + strlen(bus) - 1;
491 while (p >= bus && isdigit(*p))
492 p--;
493 *++p = '\0';
494 if (verbose_flag)
495 printf("Searching %s %s bus at %s for pnpinfo %s\n",
496 dev->dd_name, bus, dev->dd_location, dev->dd_pnpinfo);
497 search_hints(bus, dev->dd_name, dev->dd_pnpinfo);
498 free(bus);
499 } while (0);
500
501 return (devinfo_foreach_device_child(dev, find_unmatched, arg));
502 }
503
504 struct exact_info
505 {
506 const char *bus;
507 const char *loc;
508 struct devinfo_dev *dev;
509 };
510
511 /*
512 * Look for the exact location specified by the nomatch event. The
513 * loc and pnpinfo run together to get the string we're looking for,
514 * so we have to synthesize the same thing that subr_bus.c is
515 * generating in devnomatch/devaddq to do the string comparison.
516 */
517 static int
find_exact_dev(struct devinfo_dev * dev,void * arg)518 find_exact_dev(struct devinfo_dev *dev, void *arg)
519 {
520 struct devinfo_dev *parent;
521 char *loc;
522 struct exact_info *info;
523
524 info = arg;
525 do {
526 if (info->dev != NULL)
527 break;
528 if (!(dev->dd_flags & DF_ENABLED))
529 break;
530 parent = devinfo_handle_to_device(dev->dd_parent);
531 if (strcmp(info->bus, parent->dd_name) != 0)
532 break;
533 asprintf(&loc, "%s %s", parent->dd_pnpinfo,
534 parent->dd_location);
535 if (strcmp(loc, info->loc) == 0)
536 info->dev = dev;
537 free(loc);
538 } while (0);
539
540 return (devinfo_foreach_device_child(dev, find_exact_dev, arg));
541 }
542
543 static void
find_nomatch(char * nomatch)544 find_nomatch(char *nomatch)
545 {
546 char *bus, *pnpinfo, *tmp, *busnameunit;
547 struct exact_info info;
548
549 /*
550 * Find our bus name. It will include the unit number. We have to search
551 * backwards to avoid false positive for any PNP string that has ' on '
552 * in them, which would come earlier in the string. Like if there were
553 * an 'Old Bard' ethernet card made by 'Stratford on Avon Hardware' or
554 * something silly like that.
555 */
556 tmp = nomatch + strlen(nomatch) - 4;
557 while (tmp > nomatch && strncmp(tmp, " on ", 4) != 0)
558 tmp--;
559 if (tmp == nomatch)
560 errx(1, "No bus found in nomatch string: '%s'", nomatch);
561 bus = tmp + 4;
562 *tmp = '\0';
563 busnameunit = strdup(bus);
564 if (busnameunit == NULL)
565 errx(1, "Can't allocate memory for strings");
566 tmp = bus + strlen(bus) - 1;
567 while (tmp > bus && isdigit(*tmp))
568 tmp--;
569 *++tmp = '\0';
570
571 /*
572 * Note: the NOMATCH events place both the bus location as well as the
573 * pnp info after the 'at' and we don't know where one stops and the
574 * other begins, so we pass the whole thing to our search routine.
575 */
576 if (*nomatch == '?')
577 nomatch++;
578 if (strncmp(nomatch, " at ", 4) != 0)
579 errx(1, "Malformed NOMATCH string: '%s'", nomatch);
580 pnpinfo = nomatch + 4;
581
582 /*
583 * See if we can find the devinfo_dev for this device. If we
584 * can, and it's been attached before, we should filter it out
585 * so that a kldunload foo doesn't cause an immediate reload.
586 */
587 info.loc = pnpinfo;
588 info.bus = busnameunit;
589 info.dev = NULL;
590 devinfo_foreach_device_child(root, find_exact_dev, (void *)&info);
591 if (info.dev != NULL && info.dev->dd_flags & DF_ATTACHED_ONCE)
592 exit(0);
593 search_hints(bus, "", pnpinfo);
594
595 exit(0);
596 }
597
598 static void
usage(void)599 usage(void)
600 {
601
602 errx(1, "devmatch [-adv] [-p nomatch] [-h linker-hints]");
603 }
604
605 int
main(int argc,char ** argv)606 main(int argc, char **argv)
607 {
608 int ch;
609
610 while ((ch = getopt_long(argc, argv, "adh:p:quv",
611 longopts, NULL)) != -1) {
612 switch (ch) {
613 case 'a':
614 all_flag = true;
615 break;
616 case 'd':
617 dump_flag = true;
618 break;
619 case 'h':
620 linker_hints = optarg;
621 break;
622 case 'p':
623 nomatch_str = optarg;
624 break;
625 case 'q':
626 quiet_flag = true;
627 break;
628 case 'u':
629 unbound_flag = true;
630 break;
631 case 'v':
632 verbose_flag = true;
633 break;
634 default:
635 usage();
636 }
637 }
638 argc -= optind;
639 argv += optind;
640
641 if (argc >= 1)
642 usage();
643
644 read_linker_hints();
645 if (dump_flag) {
646 search_hints(NULL, NULL, NULL);
647 exit(0);
648 }
649
650 if (devinfo_init())
651 err(1, "devinfo_init");
652 if ((root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL)
653 errx(1, "can't find root device");
654 if (nomatch_str != NULL)
655 find_nomatch(nomatch_str);
656 else
657 devinfo_foreach_device_child(root, find_unmatched, (void *)0);
658 devinfo_free();
659 }
660