xref: /freebsd/sbin/devmatch/devmatch.c (revision bac7bd5038e09d12dfdbf79a87b25443e02d0ba9)
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 	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 	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, *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
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
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
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
599 usage(void)
600 {
601 
602 	errx(1, "devmatch [-adv] [-p nomatch] [-h linker-hints]");
603 }
604 
605 int
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