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