xref: /freebsd/sbin/devmatch/devmatch.c (revision 592ae60e2b2eff6c2ec467c34be9a457ce24b044)
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 *
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
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
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
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
203 pnpval_as_int(const char *val, const char *pnpinfo)
204 {
205 	int rv;
206 	char key[256];
207 	const 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
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 *
247 pnpval_as_str(const char *val, const char *pnpinfo)
248 {
249 	static char retval[256];
250 	char key[256];
251 	const 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
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;
286 	const char *s;
287 
288 	walker = hints;
289 	getint(&walker);
290 	found = 0;
291 	if (verbose_flag)
292 		printf("Searching bus %s dev %s for pnpinfo %s\n",
293 		    bus, dev, pnpinfo);
294 	while (walker < hints_end) {
295 		len = getint(&walker);
296 		ival = getint(&walker);
297 		ptr = walker;
298 		switch (ival) {
299 		case MDT_VERSION:
300 			getstr(&ptr, val1);
301 			ival = getint(&ptr);
302 			getstr(&ptr, val2);
303 			if (dump_flag || verbose_flag)
304 				printf("Version: if %s.%d kmod %s\n", val1, ival, val2);
305 			break;
306 		case MDT_MODULE:
307 			getstr(&ptr, val1);
308 			getstr(&ptr, val2);
309 			if (lastmod)
310 				free(lastmod);
311 			lastmod = strdup(val2);
312 			if (dump_flag || verbose_flag)
313 				printf("Module %s in %s\n", val1, val2);
314 			break;
315 		case MDT_PNP_INFO:
316 			if (!dump_flag && !unbound_flag && lastmod && strcmp(lastmod, "kernel") == 0)
317 				break;
318 			getstr(&ptr, val1);
319 			getstr(&ptr, val2);
320 			ents = getint(&ptr);
321 			if (dump_flag || verbose_flag)
322 				printf("PNP info for bus %s format %s %d entries (%s)\n",
323 				    val1, val2, ents, lastmod);
324 			if (strcmp(val1, "usb") == 0) {
325 				if (verbose_flag)
326 					printf("Treating usb as uhub -- bug in source table still?\n");
327 				strcpy(val1, "uhub");
328 			}
329 			if (bus && strcmp(val1, bus) != 0) {
330 				if (verbose_flag)
331 					printf("Skipped because table for bus %s, looking for %s\n",
332 					    val1, bus);
333 				break;
334 			}
335 			for (i = 0; i < ents; i++) {
336 				if (verbose_flag)
337 					printf("---------- Entry %d ----------\n", i);
338 				if (dump_flag)
339 					printf("   ");
340 				cp = val2;
341 				notme = 0;
342 				mask = -1;
343 				bit = -1;
344 				do {
345 					switch (*cp) {
346 						/* All integer fields */
347 					case 'I':
348 					case 'J':
349 					case 'G':
350 					case 'L':
351 					case 'M':
352 						ival = getint(&ptr);
353 						if (dump_flag) {
354 							printf("%#x:", ival);
355 							break;
356 						}
357 						if (bit >= 0 && ((1 << bit) & mask) == 0)
358 							break;
359 						if (cp[2] == '#') {
360 							if (verbose_flag) {
361 								printf("Ignoring %s (%c) table=%#x tomatch=%#x\n",
362 								    cp + 2, *cp, v, ival);
363 							}
364 							break;
365 						}
366 						v = pnpval_as_int(cp + 2, pnpinfo);
367 						if (verbose_flag)
368 							printf("Matching %s (%c) table=%#x tomatch=%#x\n",
369 							    cp + 2, *cp, v, ival);
370 						switch (*cp) {
371 						case 'J':
372 							if (ival == -1)
373 								break;
374 							/*FALLTHROUGH*/
375 						case 'I':
376 							if (v != ival)
377 								notme++;
378 							break;
379 						case 'G':
380 							if (v < ival)
381 								notme++;
382 							break;
383 						case 'L':
384 							if (v > ival)
385 								notme++;
386 							break;
387 						case 'M':
388 							mask = ival;
389 							break;
390 						}
391 						break;
392 						/* String fields */
393 					case 'D':
394 					case 'Z':
395 						getstr(&ptr, val1);
396 						if (dump_flag) {
397 							printf("'%s':", val1);
398 							break;
399 						}
400 						if (*cp == 'D')
401 							break;
402 						if (bit >= 0 && ((1 << bit) & mask) == 0)
403 							break;
404 						if (cp[2] == '#') {
405 							if (verbose_flag) {
406 								printf("Ignoring %s (%c) table=%#x tomatch=%#x\n",
407 								    cp + 2, *cp, v, ival);
408 							}
409 							break;
410 						}
411 						s = pnpval_as_str(cp + 2, pnpinfo);
412 						if (verbose_flag)
413 							printf("Matching %s (%c) table=%s tomatch=%s\n",
414 							    cp + 2, *cp, s, val1);
415 						if (strcmp(s, val1) != 0)
416 							notme++;
417 						break;
418 						/* Key override fields, required to be last in the string */
419 					case 'T':
420 						/*
421 						 * This is imperfect and only does one key and will be redone
422 						 * to be more general for multiple keys. Currently, nothing
423 						 * does that.
424 						 */
425 						if (dump_flag)				/* No per-row data stored */
426 							break;
427 						if (cp[strlen(cp) - 1] == ';')		/* Skip required ; at end */
428 							cp[strlen(cp) - 1] = '\0';	/* in case it's not there */
429 						if ((s = strstr(pnpinfo, cp + 2)) == NULL)
430 							notme++;
431 						else if (s > pnpinfo && s[-1] != ' ')
432 							notme++;
433 						break;
434 					default:
435 						fprintf(stderr, "Unknown field type %c\n:", *cp);
436 						break;
437 					}
438 					bit++;
439 					cp = strchr(cp, ';');
440 					if (cp)
441 						cp++;
442 				} while (cp && *cp);
443 				if (dump_flag)
444 					printf("\n");
445 				else if (!notme) {
446 					if (!unbound_flag) {
447 						if (all_flag)
448 							printf("%s: %s\n", *dev ? dev : "unattached", lastmod);
449 						else
450 							printf("%s\n", lastmod);
451 						if (verbose_flag)
452 							printf("Matches --- %s ---\n", lastmod);
453 					}
454 					found++;
455 				}
456 			}
457 			break;
458 		default:
459 			if (dump_flag)
460 				printf("Unknown Type %d len %d\n", ival, len);
461 			break;
462 		}
463 		walker = (void *)(len - sizeof(int) + (intptr_t)walker);
464 	}
465 	if (unbound_flag && found == 0 && *pnpinfo) {
466 		if (verbose_flag)
467 			printf("------------------------- ");
468 		printf("%s on %s pnpinfo %s", *dev ? dev : "unattached", bus, pnpinfo);
469 		if (verbose_flag)
470 			printf(" -------------------------");
471 		printf("\n");
472 	}
473 	free(lastmod);
474 }
475 
476 static int
477 find_unmatched(struct devinfo_dev *dev, void *arg)
478 {
479 	struct devinfo_dev *parent;
480 	char *bus, *p;
481 
482 	do {
483 		if (!all_flag && dev->dd_name[0] != '\0')
484 			break;
485 		if (!(dev->dd_flags & DF_ENABLED))
486 			break;
487 		if (!all_flag && dev->dd_flags & DF_ATTACHED_ONCE)
488 			break;
489 		parent = devinfo_handle_to_device(dev->dd_parent);
490 		bus = strdup(parent->dd_name);
491 		p = bus + strlen(bus) - 1;
492 		while (p >= bus && isdigit(*p))
493 			p--;
494 		*++p = '\0';
495 		if (verbose_flag)
496 			printf("Searching %s %s bus at %s for pnpinfo %s\n",
497 			    dev->dd_name, bus, dev->dd_location, dev->dd_pnpinfo);
498 		search_hints(bus, dev->dd_name, dev->dd_pnpinfo);
499 		free(bus);
500 	} while (0);
501 
502 	return (devinfo_foreach_device_child(dev, find_unmatched, arg));
503 }
504 
505 struct exact_info
506 {
507 	const char *bus;
508 	const char *loc;
509 	struct devinfo_dev *dev;
510 };
511 
512 /*
513  * Look for the exact location specified by the nomatch event.  The
514  * loc and pnpinfo run together to get the string we're looking for,
515  * so we have to synthesize the same thing that subr_bus.c is
516  * generating in devnomatch/devaddq to do the string comparison.
517  */
518 static int
519 find_exact_dev(struct devinfo_dev *dev, void *arg)
520 {
521 	struct devinfo_dev *parent;
522 	char *loc;
523 	struct exact_info *info;
524 
525 	info = arg;
526 	do {
527 		if (info->dev != NULL)
528 			break;
529 		if (!(dev->dd_flags & DF_ENABLED))
530 			break;
531 		parent = devinfo_handle_to_device(dev->dd_parent);
532 		if (strcmp(info->bus, parent->dd_name) != 0)
533 			break;
534 		asprintf(&loc, "%s %s", parent->dd_pnpinfo,
535 		    parent->dd_location);
536 		if (strcmp(loc, info->loc) == 0)
537 			info->dev = dev;
538 		free(loc);
539 	} while (0);
540 
541 	return (devinfo_foreach_device_child(dev, find_exact_dev, arg));
542 }
543 
544 static void
545 find_nomatch(char *nomatch)
546 {
547 	char *bus, *pnpinfo, *tmp, *busnameunit;
548 	struct exact_info info;
549 
550 	/*
551 	 * Find our bus name. It will include the unit number. We have to search
552 	 * backwards to avoid false positive for any PNP string that has ' on '
553 	 * in them, which would come earlier in the string. Like if there were
554 	 * an 'Old Bard' ethernet card made by 'Stratford on Avon Hardware' or
555 	 * something silly like that.
556 	 */
557 	tmp = nomatch + strlen(nomatch) - 4;
558 	while (tmp > nomatch && strncmp(tmp, " on ", 4) != 0)
559 		tmp--;
560 	if (tmp == nomatch)
561 		errx(1, "No bus found in nomatch string: '%s'", nomatch);
562 	bus = tmp + 4;
563 	*tmp = '\0';
564 	busnameunit = strdup(bus);
565 	if (busnameunit == NULL)
566 		errx(1, "Can't allocate memory for strings");
567 	tmp = bus + strlen(bus) - 1;
568 	while (tmp > bus && isdigit(*tmp))
569 		tmp--;
570 	*++tmp = '\0';
571 
572 	/*
573 	 * Note: the NOMATCH events place both the bus location as well as the
574 	 * pnp info after the 'at' and we don't know where one stops and the
575 	 * other begins, so we pass the whole thing to our search routine.
576 	 */
577 	if (*nomatch == '?')
578 		nomatch++;
579 	if (strncmp(nomatch, " at ", 4) != 0)
580 		errx(1, "Malformed NOMATCH string: '%s'", nomatch);
581 	pnpinfo = nomatch + 4;
582 
583 	/*
584 	 * See if we can find the devinfo_dev for this device. If we
585 	 * can, and it's been attached before, we should filter it out
586 	 * so that a kldunload foo doesn't cause an immediate reload.
587 	 */
588 	info.loc = pnpinfo;
589 	info.bus = busnameunit;
590 	info.dev = NULL;
591 	devinfo_foreach_device_child(root, find_exact_dev, (void *)&info);
592 	if (info.dev != NULL && info.dev->dd_flags & DF_ATTACHED_ONCE)
593 		exit(0);
594 	search_hints(bus, "", pnpinfo);
595 
596 	exit(0);
597 }
598 
599 static void
600 usage(void)
601 {
602 
603 	errx(1, "devmatch [-adv] [-p nomatch] [-h linker-hints]");
604 }
605 
606 int
607 main(int argc, char **argv)
608 {
609 	int ch;
610 
611 	while ((ch = getopt_long(argc, argv, "adh:p:quv",
612 		    longopts, NULL)) != -1) {
613 		switch (ch) {
614 		case 'a':
615 			all_flag = true;
616 			break;
617 		case 'd':
618 			dump_flag = true;
619 			break;
620 		case 'h':
621 			linker_hints = optarg;
622 			break;
623 		case 'p':
624 			nomatch_str = optarg;
625 			break;
626 		case 'q':
627 			quiet_flag = true;
628 			break;
629 		case 'u':
630 			unbound_flag = true;
631 			break;
632 		case 'v':
633 			verbose_flag = true;
634 			break;
635 		default:
636 			usage();
637 		}
638 	}
639 	argc -= optind;
640 	argv += optind;
641 
642 	if (argc >= 1)
643 		usage();
644 
645 	read_linker_hints();
646 	if (dump_flag) {
647 		search_hints(NULL, NULL, NULL);
648 		exit(0);
649 	}
650 
651 	if (devinfo_init())
652 		err(1, "devinfo_init");
653 	if ((root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL)
654 		errx(1, "can't find root device");
655 	if (nomatch_str != NULL)
656 		find_nomatch(nomatch_str);
657 	else
658 		devinfo_foreach_device_child(root, find_unmatched, (void *)0);
659 	devinfo_free();
660 }
661