xref: /illumos-gate/usr/src/cmd/pcidb/pcidb.c (revision 87ae43a96ad65ea96ee2441fce68d34a01486168)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2024 Oxide Computer Company
14  */
15 
16 /*
17  * A tool to interface with the pci.ids database driven by libpcidb.
18  */
19 
20 #include <stdio.h>
21 #include <stdarg.h>
22 #include <pcidb.h>
23 #include <err.h>
24 #include <libgen.h>
25 #include <string.h>
26 #include <strings.h>
27 #include <stdlib.h>
28 #include <ofmt.h>
29 #include <errno.h>
30 #include <sys/debug.h>
31 #include <priv.h>
32 
33 #define	EXIT_USAGE	2
34 
35 static char *pcidb_progname;
36 
37 typedef enum {
38 	PCIDB_MODE_UNKNOWN,
39 	PCIDB_MODE_LIST,
40 	PCIDB_MODE_SEARCH,
41 	PCIDB_MODE_LOOKUP
42 } pcidb_mode_t;
43 
44 typedef enum {
45 	PCIDB_TABLE_NONE,
46 	PCIDB_TABLE_VENDOR,
47 	PCIDB_TABLE_DEVICE,
48 	PCIDB_TABLE_SUBSYSTEM,
49 	PCIDB_TABLE_CLASS,
50 	PCIDB_TABLE_SUBCLASS,
51 	PCIDB_TABLE_PROGIF
52 } pcidb_table_t;
53 
54 typedef enum {
55 	PCIDB_TGRP_NONE,
56 	PCIDB_TGRP_DEV,
57 	PCIDB_TGRP_CLASS
58 } pcidb_tgrp_t;
59 
60 typedef enum {
61 	PCIDB_OFMT_VID,
62 	PCIDB_OFMT_VENSTR,
63 	PCIDB_OFMT_DID,
64 	PCIDB_OFMT_DEVSTR,
65 	PCIDB_OFMT_SVID,
66 	PCIDB_OFMT_SDID,
67 	PCIDB_OFMT_SUBVENSTR,
68 	PCIDB_OFMT_SUBSYSSTR,
69 	PCIDB_OFMT_BCC,
70 	PCIDB_OFMT_CLASSSTR,
71 	PCIDB_OFMT_SCC,
72 	PCIDB_OFMT_SUBCLASSSTR,
73 	PCIDB_OFMT_PI,
74 	PCIDB_OFMT_PROGIFSTR
75 } pcidb_ofmt_t;
76 
77 typedef struct pcidb_filter {
78 	const char *pft_raw;
79 	boolean_t pft_used;
80 	pcidb_table_t pft_table;
81 	pcidb_tgrp_t pft_tgrp;
82 	uint32_t pft_vend;
83 	uint32_t pft_dev;
84 	uint32_t pft_subven;
85 	uint32_t pft_subdev;
86 	uint32_t pft_class;
87 	uint32_t pft_subclass;
88 	uint32_t pft_progif;
89 } pcidb_filter_t;
90 
91 #define	PCIDB_NOFILTER	UINT32_MAX
92 
93 typedef struct pcidb_walk {
94 	pcidb_hdl_t *pw_hdl;
95 	ofmt_handle_t pw_ofmt;
96 	pcidb_vendor_t *pw_vendor;
97 	pcidb_device_t *pw_device;
98 	pcidb_subvd_t *pw_subvd;
99 	pcidb_class_t *pw_class;
100 	pcidb_subclass_t *pw_subclass;
101 	pcidb_progif_t *pw_progif;
102 	boolean_t pw_strcase;
103 	uint_t pw_nfilters;
104 	pcidb_filter_t *pw_filters;
105 } pcidb_walk_t;
106 
107 static boolean_t
pcidb_write_vendor(ofmt_arg_t * ofarg,char * buf,uint_t buflen)108 pcidb_write_vendor(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
109 {
110 	pcidb_walk_t *walk = ofarg->ofmt_cbarg;
111 
112 	VERIFY(walk->pw_vendor != NULL);
113 	switch (ofarg->ofmt_id) {
114 	case PCIDB_OFMT_VID:
115 		(void) snprintf(buf, buflen, "%x",
116 		    pcidb_vendor_id(walk->pw_vendor));
117 		break;
118 	case PCIDB_OFMT_VENSTR:
119 		(void) strlcpy(buf, pcidb_vendor_name(walk->pw_vendor), buflen);
120 		break;
121 	default:
122 		abort();
123 	}
124 	return (B_TRUE);
125 }
126 
127 static boolean_t
pcidb_write_device(ofmt_arg_t * ofarg,char * buf,uint_t buflen)128 pcidb_write_device(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
129 {
130 	pcidb_walk_t *walk = ofarg->ofmt_cbarg;
131 
132 	VERIFY(walk->pw_device != NULL);
133 	switch (ofarg->ofmt_id) {
134 	case PCIDB_OFMT_DID:
135 		(void) snprintf(buf, buflen, "%x",
136 		    pcidb_device_id(walk->pw_device));
137 		break;
138 	case PCIDB_OFMT_DEVSTR:
139 		(void) strlcpy(buf, pcidb_device_name(walk->pw_device), buflen);
140 		break;
141 	default:
142 		abort();
143 	}
144 	return (B_TRUE);
145 }
146 
147 static boolean_t
pcidb_write_subsystem(ofmt_arg_t * ofarg,char * buf,uint_t buflen)148 pcidb_write_subsystem(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
149 {
150 	pcidb_walk_t *walk = ofarg->ofmt_cbarg;
151 	pcidb_vendor_t *vendor;
152 
153 	VERIFY(walk->pw_subvd != NULL);
154 	switch (ofarg->ofmt_id) {
155 	case PCIDB_OFMT_SVID:
156 		(void) snprintf(buf, buflen, "%x",
157 		    pcidb_subvd_svid(walk->pw_subvd));
158 		break;
159 	case PCIDB_OFMT_SDID:
160 		(void) snprintf(buf, buflen, "%x",
161 		    pcidb_subvd_sdid(walk->pw_subvd));
162 		break;
163 	case PCIDB_OFMT_SUBSYSSTR:
164 		(void) strlcpy(buf, pcidb_subvd_name(walk->pw_subvd), buflen);
165 		break;
166 	case PCIDB_OFMT_SUBVENSTR:
167 		vendor = pcidb_lookup_vendor(walk->pw_hdl,
168 		    pcidb_subvd_svid(walk->pw_subvd));
169 		if (vendor == NULL) {
170 			return (B_FALSE);
171 		}
172 		(void) strlcpy(buf, pcidb_vendor_name(vendor), buflen);
173 		break;
174 	default:
175 		abort();
176 	}
177 	return (B_TRUE);
178 }
179 
180 static boolean_t
pcidb_write_class(ofmt_arg_t * ofarg,char * buf,uint_t buflen)181 pcidb_write_class(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
182 {
183 	pcidb_walk_t *walk = ofarg->ofmt_cbarg;
184 
185 	VERIFY(walk->pw_class != NULL);
186 	switch (ofarg->ofmt_id) {
187 	case PCIDB_OFMT_BCC:
188 		(void) snprintf(buf, buflen, "%x",
189 		    pcidb_class_code(walk->pw_class));
190 		break;
191 	case PCIDB_OFMT_CLASSSTR:
192 		(void) strlcpy(buf, pcidb_class_name(walk->pw_class), buflen);
193 		break;
194 	default:
195 		abort();
196 	}
197 	return (B_TRUE);
198 }
199 
200 static boolean_t
pcidb_write_subclass(ofmt_arg_t * ofarg,char * buf,uint_t buflen)201 pcidb_write_subclass(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
202 {
203 	pcidb_walk_t *walk = ofarg->ofmt_cbarg;
204 
205 	VERIFY(walk->pw_subclass != NULL);
206 	switch (ofarg->ofmt_id) {
207 	case PCIDB_OFMT_SCC:
208 		(void) snprintf(buf, buflen, "%x",
209 		    pcidb_subclass_code(walk->pw_subclass));
210 		break;
211 	case PCIDB_OFMT_SUBCLASSSTR:
212 		(void) strlcpy(buf, pcidb_subclass_name(walk->pw_subclass),
213 		    buflen);
214 		break;
215 	default:
216 		abort();
217 	}
218 	return (B_TRUE);
219 }
220 
221 static boolean_t
pcidb_write_progif(ofmt_arg_t * ofarg,char * buf,uint_t buflen)222 pcidb_write_progif(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
223 {
224 	pcidb_walk_t *walk = ofarg->ofmt_cbarg;
225 
226 	VERIFY(walk->pw_progif != NULL);
227 	switch (ofarg->ofmt_id) {
228 	case PCIDB_OFMT_PI:
229 		(void) snprintf(buf, buflen, "%x",
230 		    pcidb_progif_code(walk->pw_progif));
231 		break;
232 	case PCIDB_OFMT_PROGIFSTR:
233 		(void) strlcpy(buf, pcidb_progif_name(walk->pw_progif),
234 		    buflen);
235 		break;
236 	default:
237 		abort();
238 	}
239 	return (B_TRUE);
240 }
241 
242 static const char *pcidb_vendor_fields = "vid,vendor";
243 static const ofmt_field_t pcidb_vendor_ofmt[] = {
244 	{ "VID",	8,	PCIDB_OFMT_VID,		pcidb_write_vendor },
245 	{ "VENDOR",	30,	PCIDB_OFMT_VENSTR,	pcidb_write_vendor },
246 	{ NULL, 0, 0, NULL }
247 };
248 
249 static const char *pcidb_device_fields = "vid,did,vendor,device";
250 static const ofmt_field_t pcidb_device_ofmt[] = {
251 	{ "VID",	8,	PCIDB_OFMT_VID,		pcidb_write_vendor },
252 	{ "VENDOR",	30,	PCIDB_OFMT_VENSTR,	pcidb_write_vendor },
253 	{ "DID",	8,	PCIDB_OFMT_DID,		pcidb_write_device },
254 	{ "DEVICE",	30,	PCIDB_OFMT_DEVSTR,	pcidb_write_device },
255 	{ NULL, 0, 0, NULL }
256 };
257 
258 static const char *pcidb_subsystem_fields = "vid,did,svid,sdid,subsystem";
259 static const ofmt_field_t pcidb_subsystem_ofmt[] = {
260 	{ "VID",	8,	PCIDB_OFMT_VID,		pcidb_write_vendor },
261 	{ "VENDOR",	30,	PCIDB_OFMT_VENSTR,	pcidb_write_vendor },
262 	{ "DID",	8,	PCIDB_OFMT_DID,		pcidb_write_device },
263 	{ "DEVICE",	30,	PCIDB_OFMT_DEVSTR,	pcidb_write_device },
264 	{ "SVID",	8,	PCIDB_OFMT_SVID,	pcidb_write_subsystem },
265 	{ "SDID",	8,	PCIDB_OFMT_SDID,	pcidb_write_subsystem },
266 	{ "SUBSYSTEM",	30,	PCIDB_OFMT_SUBSYSSTR,	pcidb_write_subsystem },
267 	{ "SUBVENDOR",	30,	PCIDB_OFMT_SUBVENSTR,	pcidb_write_subsystem },
268 	{ NULL, 0, 0, NULL }
269 };
270 
271 static const char *pcidb_class_fields = "bcc,class";
272 static const ofmt_field_t pcidb_class_ofmt[] = {
273 	{ "BCC",	6,	PCIDB_OFMT_BCC,		pcidb_write_class },
274 	{ "CLASS",	30,	PCIDB_OFMT_CLASSSTR,	pcidb_write_class },
275 	{ NULL, 0, 0, NULL }
276 };
277 
278 static const char *pcidb_subclass_fields = "bcc,scc,class,subclass";
279 static const ofmt_field_t pcidb_subclass_ofmt[] = {
280 	{ "BCC",	6,	PCIDB_OFMT_BCC,		pcidb_write_class },
281 	{ "CLASS",	30,	PCIDB_OFMT_CLASSSTR,	pcidb_write_class },
282 	{ "SCC",	6,	PCIDB_OFMT_SCC,		pcidb_write_subclass },
283 	{ "SUBCLASS",	30,	PCIDB_OFMT_SUBCLASSSTR,	pcidb_write_subclass },
284 	{ NULL, 0, 0, NULL }
285 };
286 
287 static const char *pcidb_progif_fields = "bcc,scc,pi,subclass,interface";
288 static const ofmt_field_t pcidb_progif_ofmt[] = {
289 	{ "BCC",	6,	PCIDB_OFMT_BCC,		pcidb_write_class },
290 	{ "CLASS",	30,	PCIDB_OFMT_CLASSSTR,	pcidb_write_class },
291 	{ "SCC",	6,	PCIDB_OFMT_SCC,		pcidb_write_subclass },
292 	{ "SUBCLASS",	30,	PCIDB_OFMT_SUBCLASSSTR,	pcidb_write_subclass },
293 	{ "PI",		6,	PCIDB_OFMT_PI,		pcidb_write_progif },
294 	{ "INTERFACE",	30,	PCIDB_OFMT_PROGIFSTR,	pcidb_write_progif },
295 	{ NULL, 0, 0, NULL }
296 };
297 
298 static void
pcidb_ofmt_errx(const char * fmt,...)299 pcidb_ofmt_errx(const char *fmt, ...)
300 {
301 	va_list ap;
302 
303 	va_start(ap, fmt);
304 	verrx(EXIT_FAILURE, fmt, ap);
305 }
306 
307 /*
308  * Check to see if any of our filters match. Note, our filters may overlap and
309  * one may be more specific than another. As a result, once we find a match we
310  * check all remaining filters and check if any of them would also match this
311  * just to reduce the chance of user error. For example, if we didn't do this a
312  * series of filters such as "pci8086 pci8086,10d3" will cause the latter to
313  * never be matched.
314  */
315 static boolean_t
pcidb_filter_match(pcidb_walk_t * walk)316 pcidb_filter_match(pcidb_walk_t *walk)
317 {
318 	boolean_t match = B_FALSE;
319 	pcidb_tgrp_t grp = PCIDB_TGRP_NONE;
320 
321 	if (walk->pw_nfilters == 0) {
322 		return (B_TRUE);
323 	}
324 
325 	for (uint_t i = 0; i < walk->pw_nfilters; i++) {
326 		pcidb_filter_t *filt = &walk->pw_filters[i];
327 
328 		if (match && (filt->pft_used || grp != filt->pft_tgrp)) {
329 			continue;
330 		}
331 
332 		if (filt->pft_vend != PCIDB_NOFILTER &&
333 		    (walk->pw_vendor == NULL ||
334 		    filt->pft_vend != pcidb_vendor_id(walk->pw_vendor))) {
335 			continue;
336 		}
337 
338 		if (filt->pft_dev != PCIDB_NOFILTER &&
339 		    (walk->pw_device == NULL ||
340 		    filt->pft_dev != pcidb_device_id(walk->pw_device))) {
341 			continue;
342 		}
343 
344 		if (filt->pft_subven != PCIDB_NOFILTER &&
345 		    (walk->pw_subvd == NULL ||
346 		    filt->pft_subven != pcidb_subvd_svid(walk->pw_subvd))) {
347 			continue;
348 		}
349 
350 		if (filt->pft_subdev != PCIDB_NOFILTER &&
351 		    (walk->pw_subvd == NULL ||
352 		    filt->pft_subdev != pcidb_subvd_sdid(walk->pw_subvd))) {
353 			continue;
354 		}
355 
356 		if (filt->pft_class != PCIDB_NOFILTER &&
357 		    (walk->pw_class == NULL ||
358 		    filt->pft_class != pcidb_class_code(walk->pw_class))) {
359 			continue;
360 		}
361 
362 		if (filt->pft_subclass != PCIDB_NOFILTER &&
363 		    (walk->pw_subclass == NULL ||
364 		    filt->pft_subclass !=
365 		    pcidb_subclass_code(walk->pw_subclass))) {
366 			continue;
367 		}
368 
369 		if (filt->pft_progif != PCIDB_NOFILTER &&
370 		    (walk->pw_progif == NULL ||
371 		    filt->pft_progif != pcidb_progif_code(walk->pw_progif))) {
372 			continue;
373 		}
374 
375 		filt->pft_used = B_TRUE;
376 		grp = filt->pft_tgrp;
377 		match = B_TRUE;
378 	}
379 
380 	return (match);
381 }
382 
383 static void
pcidb_walk_vendors(pcidb_walk_t * walk)384 pcidb_walk_vendors(pcidb_walk_t *walk)
385 {
386 	pcidb_hdl_t *hdl = walk->pw_hdl;
387 
388 	for (pcidb_vendor_t *vend = pcidb_vendor_iter(hdl); vend != NULL;
389 	    vend = pcidb_vendor_iter_next(vend)) {
390 		walk->pw_vendor = vend;
391 		if (!pcidb_filter_match(walk))
392 			continue;
393 		ofmt_print(walk->pw_ofmt, walk);
394 	}
395 }
396 
397 static void
pcidb_walk_devices(pcidb_walk_t * walk)398 pcidb_walk_devices(pcidb_walk_t *walk)
399 {
400 	pcidb_hdl_t *hdl = walk->pw_hdl;
401 
402 	for (pcidb_vendor_t *vend = pcidb_vendor_iter(hdl); vend != NULL;
403 	    vend = pcidb_vendor_iter_next(vend)) {
404 		walk->pw_vendor = vend;
405 		for (pcidb_device_t *dev = pcidb_device_iter(vend); dev != NULL;
406 		    dev = pcidb_device_iter_next(dev)) {
407 			walk->pw_device = dev;
408 			if (!pcidb_filter_match(walk))
409 				continue;
410 			ofmt_print(walk->pw_ofmt, walk);
411 		}
412 	}
413 }
414 
415 static void
pcidb_walk_subsystems(pcidb_walk_t * walk)416 pcidb_walk_subsystems(pcidb_walk_t *walk)
417 {
418 	pcidb_hdl_t *hdl = walk->pw_hdl;
419 
420 	for (pcidb_vendor_t *vend = pcidb_vendor_iter(hdl); vend != NULL;
421 	    vend = pcidb_vendor_iter_next(vend)) {
422 		walk->pw_vendor = vend;
423 		for (pcidb_device_t *dev = pcidb_device_iter(vend); dev != NULL;
424 		    dev = pcidb_device_iter_next(dev)) {
425 			walk->pw_device = dev;
426 			for (pcidb_subvd_t *sub = pcidb_subvd_iter(dev);
427 			    sub != NULL; sub = pcidb_subvd_iter_next(sub)) {
428 				walk->pw_subvd = sub;
429 				if (!pcidb_filter_match(walk))
430 					continue;
431 				ofmt_print(walk->pw_ofmt, walk);
432 			}
433 		}
434 
435 	}
436 }
437 
438 static void
pcidb_walk_classes(pcidb_walk_t * walk)439 pcidb_walk_classes(pcidb_walk_t *walk)
440 {
441 	for (pcidb_class_t *class = pcidb_class_iter(walk->pw_hdl);
442 	    class != NULL; class = pcidb_class_iter_next(class)) {
443 		walk->pw_class = class;
444 		if (!pcidb_filter_match(walk))
445 			continue;
446 		ofmt_print(walk->pw_ofmt, walk);
447 	}
448 }
449 
450 static void
pcidb_walk_subclasses(pcidb_walk_t * walk)451 pcidb_walk_subclasses(pcidb_walk_t *walk)
452 {
453 	for (pcidb_class_t *class = pcidb_class_iter(walk->pw_hdl);
454 	    class != NULL; class = pcidb_class_iter_next(class)) {
455 		walk->pw_class = class;
456 		for (pcidb_subclass_t *sub = pcidb_subclass_iter(class);
457 		    sub != NULL; sub = pcidb_subclass_iter_next(sub)) {
458 			walk->pw_subclass = sub;
459 			if (!pcidb_filter_match(walk))
460 				continue;
461 			ofmt_print(walk->pw_ofmt, walk);
462 		}
463 	}
464 }
465 
466 static void
pcidb_walk_progifs(pcidb_walk_t * walk)467 pcidb_walk_progifs(pcidb_walk_t *walk)
468 {
469 	for (pcidb_class_t *class = pcidb_class_iter(walk->pw_hdl);
470 	    class != NULL; class = pcidb_class_iter_next(class)) {
471 		walk->pw_class = class;
472 		for (pcidb_subclass_t *sub = pcidb_subclass_iter(class);
473 		    sub != NULL; sub = pcidb_subclass_iter_next(sub)) {
474 			walk->pw_subclass = sub;
475 			for (pcidb_progif_t *progif = pcidb_progif_iter(sub);
476 			    progif != NULL;
477 			    progif = pcidb_progif_iter_next(progif)) {
478 				walk->pw_progif = progif;
479 				if (!pcidb_filter_match(walk))
480 					continue;
481 				ofmt_print(walk->pw_ofmt, walk);
482 			}
483 		}
484 	}
485 }
486 
487 static void
pcidb_parse_class_filter(pcidb_filter_t * filter,char * arg,const char * orig)488 pcidb_parse_class_filter(pcidb_filter_t *filter, char *arg, const char *orig)
489 {
490 	size_t len;
491 	unsigned long val;
492 	char *eptr;
493 
494 	filter->pft_vend = filter->pft_dev = PCIDB_NOFILTER;
495 	filter->pft_subven = filter->pft_subdev = PCIDB_NOFILTER;
496 
497 	len = strlen(arg);
498 	if (len != 2 && len != 4 && len != 6) {
499 		errx(EXIT_FAILURE, "invalid class filter: '%s': bad length",
500 		    orig);
501 	}
502 
503 	errno = 0;
504 	val = strtoul(arg, &eptr, 16);
505 	if (errno != 0 || *eptr != '\0') {
506 		errx(EXIT_FAILURE, "invalid class filter: '%s': failed to "
507 		    "parse hex string", orig);
508 	}
509 
510 	if (len == 6) {
511 		filter->pft_progif = val & 0xff;
512 		val = val >> 8;
513 	} else {
514 		filter->pft_progif = PCIDB_NOFILTER;
515 	}
516 
517 	if (len >= 4) {
518 		filter->pft_subclass = val & 0xff;
519 		val = val >> 8;
520 	} else {
521 		filter->pft_subclass = PCIDB_NOFILTER;
522 	}
523 
524 	filter->pft_class = val & 0xff;
525 }
526 
527 static void
pcidb_parse_device_filter(pcidb_filter_t * filter,char * arg,const char * orig)528 pcidb_parse_device_filter(pcidb_filter_t *filter, char *arg, const char *orig)
529 {
530 	unsigned long val;
531 	uint32_t primary, secondary;
532 	char *eptr;
533 
534 	filter->pft_vend = filter->pft_dev = PCIDB_NOFILTER;
535 	filter->pft_subven = filter->pft_subdev = PCIDB_NOFILTER;
536 	filter->pft_class = filter->pft_subclass = PCIDB_NOFILTER;
537 	filter->pft_progif = PCIDB_NOFILTER;
538 
539 	errno = 0;
540 	val = strtoul(arg, &eptr, 16);
541 	if (errno != 0 || (*eptr != '\0' && *eptr != ',')) {
542 		errx(EXIT_FAILURE, "invalid device filter: '%s': failed to "
543 		    "parse hex string", orig);
544 	}
545 
546 	if (val > UINT16_MAX) {
547 		errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff",
548 		    val);
549 	}
550 
551 	primary = (uint32_t)val;
552 	if (*eptr == '\0') {
553 		filter->pft_vend = primary;
554 		return;
555 	} else if (strcmp(eptr, ",s") == 0) {
556 		filter->pft_subven = primary;
557 		return;
558 	} else if (eptr[1] == '\0') {
559 		errx(EXIT_FAILURE, "invalid device filter: '%s': filter "
560 		    "terminated early", arg);
561 	}
562 
563 	arg = eptr + 1;
564 	val = strtoul(arg, &eptr, 16);
565 	if (errno != 0 || (*eptr != '\0' && *eptr != ',' && *eptr != '.')) {
566 		errx(EXIT_FAILURE, "invalid device filter: '%s': failed to "
567 		    "parse hex string at %s", orig, arg);
568 	}
569 
570 	if (val > UINT16_MAX) {
571 		errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff",
572 		    val);
573 	}
574 
575 	secondary = (uint32_t)val;
576 	if (*eptr == '\0') {
577 		filter->pft_vend = primary;
578 		filter->pft_dev = secondary;
579 		return;
580 	} else if (eptr[1] == '\0') {
581 		errx(EXIT_FAILURE, "invalid device filter: '%s': filter "
582 		    "terminated early", arg);
583 	}
584 
585 	if (*eptr == ',') {
586 		if (eptr[1] == 'p' && eptr[2] == '\0') {
587 			filter->pft_vend = primary;
588 			filter->pft_dev = secondary;
589 			return;
590 		}
591 		if (eptr[1] == 's' && eptr[2] == '\0') {
592 			filter->pft_subven = primary;
593 			filter->pft_subdev = secondary;
594 			return;
595 		}
596 		errx(EXIT_FAILURE, "invalid device filter: '%s': invalid "
597 		    "trailing comma at %s, expected either ,p or ,s",
598 		    orig, eptr);
599 	}
600 
601 	filter->pft_vend = primary;
602 	filter->pft_dev = secondary;
603 
604 	arg = eptr + 1;
605 	errno = 0;
606 	val = strtoul(arg, &eptr, 16);
607 	if (errno != 0 || (*eptr != '\0' && *eptr != ',')) {
608 		errx(EXIT_FAILURE, "invalid device filter: '%s': failed to "
609 		    "parse hex string at %s", orig, arg);
610 	}
611 
612 	if (val > UINT16_MAX) {
613 		errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff",
614 		    val);
615 	}
616 
617 	filter->pft_subven = (uint32_t)val;
618 	if (*eptr == '\0') {
619 		return;
620 	} else if (eptr[1] == '\0') {
621 		errx(EXIT_FAILURE, "invalid device filter: '%s': filter "
622 		    "terminated early", arg);
623 	}
624 
625 	arg = eptr + 1;
626 	errno = 0;
627 	val = strtoul(arg, &eptr, 16);
628 	if (errno != 0 || *eptr != '\0') {
629 		errx(EXIT_FAILURE, "invalid device filter: '%s': failed to "
630 		    "parse hex string at %s", orig, arg);
631 	}
632 
633 	if (val > UINT16_MAX) {
634 		errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff",
635 		    val);
636 	}
637 
638 	filter->pft_subdev = (uint32_t)val;
639 }
640 
641 static pcidb_table_t
pcidb_filter_to_table(const pcidb_filter_t * filter)642 pcidb_filter_to_table(const pcidb_filter_t *filter)
643 {
644 	if (filter->pft_progif != PCIDB_NOFILTER) {
645 		return (PCIDB_TABLE_PROGIF);
646 	} else if (filter->pft_subclass != PCIDB_NOFILTER) {
647 		return (PCIDB_TABLE_SUBCLASS);
648 	} else if (filter->pft_class != PCIDB_NOFILTER) {
649 		return (PCIDB_TABLE_CLASS);
650 	} else if (filter->pft_subven != PCIDB_NOFILTER ||
651 	    filter->pft_subdev != PCIDB_NOFILTER) {
652 		return (PCIDB_TABLE_SUBSYSTEM);
653 	} else if (filter->pft_dev != PCIDB_NOFILTER) {
654 		return (PCIDB_TABLE_DEVICE);
655 	} else {
656 		VERIFY3U(filter->pft_vend, !=, PCIDB_NOFILTER);
657 		return (PCIDB_TABLE_VENDOR);
658 	}
659 }
660 
661 static const char *
pcidb_table_to_string(pcidb_table_t table)662 pcidb_table_to_string(pcidb_table_t table)
663 {
664 	switch (table) {
665 	case PCIDB_TABLE_VENDOR:
666 		return ("vendor");
667 	case PCIDB_TABLE_DEVICE:
668 		return ("device");
669 	case PCIDB_TABLE_SUBSYSTEM:
670 		return ("subsystem");
671 	case PCIDB_TABLE_CLASS:
672 		return ("class");
673 	case PCIDB_TABLE_SUBCLASS:
674 		return ("subclass");
675 	case PCIDB_TABLE_PROGIF:
676 		return ("programming interface");
677 	case PCIDB_TABLE_NONE:
678 		return ("none");
679 	default:
680 		abort();
681 	}
682 
683 }
684 
685 static pcidb_tgrp_t
pcidb_table_to_group(pcidb_table_t table)686 pcidb_table_to_group(pcidb_table_t table)
687 {
688 	switch (table) {
689 	case PCIDB_TABLE_VENDOR:
690 	case PCIDB_TABLE_DEVICE:
691 	case PCIDB_TABLE_SUBSYSTEM:
692 		return (PCIDB_TGRP_DEV);
693 	case PCIDB_TABLE_CLASS:
694 	case PCIDB_TABLE_SUBCLASS:
695 	case PCIDB_TABLE_PROGIF:
696 		return (PCIDB_TGRP_CLASS);
697 	case PCIDB_TABLE_NONE:
698 		return (PCIDB_TGRP_NONE);
699 	default:
700 		abort();
701 	}
702 }
703 
704 /*
705  * PCIDB_TABLE_NONE is not in here as it should only be a sentinal value and not
706  * something that users see.
707  */
708 static const char *
pcidb_table_to_grpstr(pcidb_table_t table)709 pcidb_table_to_grpstr(pcidb_table_t table)
710 {
711 	switch (pcidb_table_to_group(table)) {
712 	case PCIDB_TGRP_DEV:
713 		return ("vendor/device/subsystem");
714 	case PCIDB_TGRP_CLASS:
715 		return ("class/subclass/progif");
716 	default:
717 		abort();
718 	}
719 }
720 
721 /*
722  * Process a series of alias style ways of indicating numeric filters. Use the
723  * basic alias format for now.
724  */
725 static void
pcidb_parse_filters(int argc,char * argv[],pcidb_walk_t * walkp)726 pcidb_parse_filters(int argc, char *argv[], pcidb_walk_t *walkp)
727 {
728 	if (argc <= 0) {
729 		walkp->pw_nfilters = 0;
730 		return;
731 	}
732 
733 	walkp->pw_nfilters = argc;
734 	walkp->pw_filters = calloc(walkp->pw_nfilters, sizeof (pcidb_filter_t));
735 	if (walkp->pw_filters == NULL) {
736 		err(EXIT_FAILURE, "failed to allocate memory for filters");
737 	}
738 
739 	for (int i = 0; i < argc; i++) {
740 		char *str = strdup(argv[i]);
741 
742 		if (str == NULL) {
743 			errx(EXIT_FAILURE, "failed to duplicate string %s",
744 			    argv[i]);
745 		}
746 
747 		if (strncmp(str, "pciexclass,", 11) == 0) {
748 			pcidb_parse_class_filter(&walkp->pw_filters[i],
749 			    str + 11, argv[i]);
750 		} else if (strncmp(str, "pciclass,", 9) == 0) {
751 			pcidb_parse_class_filter(&walkp->pw_filters[i], str + 9,
752 			    argv[i]);
753 		} else if (strncmp(str, "pciex", 5) == 0) {
754 			pcidb_parse_device_filter(&walkp->pw_filters[i],
755 			    str + 5, argv[i]);
756 		} else if (strncmp(str, "pci", 3) == 0) {
757 			pcidb_parse_device_filter(&walkp->pw_filters[i],
758 			    str + 3, argv[i]);
759 		} else {
760 			errx(EXIT_FAILURE, "invalid filter string: %s", str);
761 		}
762 
763 		free(str);
764 		walkp->pw_filters[i].pft_raw = argv[i];
765 		walkp->pw_filters[i].pft_used = B_FALSE;
766 		walkp->pw_filters[i].pft_table =
767 		    pcidb_filter_to_table(&walkp->pw_filters[i]);
768 		walkp->pw_filters[i].pft_tgrp =
769 		    pcidb_table_to_group(walkp->pw_filters[i].pft_table);
770 	}
771 }
772 
773 /*
774  * Determine if the set of filters is mutually consistent with the filters that
775  * we have been requested. Our goal is to prevent a user from specifying
776  * something that is basically unsatisfiable. For example, if they ask for a
777  * filter related to devices but have specified the class table or vice versa.
778  * Similarly, if someone has specified no table on the command line then we
779  * should pick a default that is actually usable. As such we have a few rules
780  * that we check:
781  *
782  *   - All filters must be in the same group of table. That is either in the
783  *     vendor/device/subsystem group or the class/subclass/progif group.
784  *   - A less specific filter for a group is always valid for a more specific
785  *     table.
786  *   - A more specific filter for a group is always invalid for a less specific
787  *     table.
788  *   - If the user did not request the table, we can automatically move the
789  *     filter to the more specific value.
790  */
791 static void
pcidb_validate_filters(const pcidb_walk_t * walk,pcidb_table_t * table)792 pcidb_validate_filters(const pcidb_walk_t *walk, pcidb_table_t *table)
793 {
794 	pcidb_table_t cur = *table;
795 	boolean_t tset = cur != PCIDB_TABLE_NONE;
796 
797 	for (uint_t i = 0; i < walk->pw_nfilters; i++) {
798 		const pcidb_filter_t *filt = &walk->pw_filters[i];
799 
800 		/*
801 		 * If we have the same table as the current one, then there is
802 		 * nothing to do.
803 		 */
804 		if (cur == filt->pft_table) {
805 			continue;
806 		}
807 
808 		/*
809 		 * When there is no current table, which implies tset is false,
810 		 * then we can always change this around. Note, if someone asks
811 		 * for the vendor table, we want to try to use the device table
812 		 * as the default like when there are no filters specified.
813 		 */
814 		if (cur == PCIDB_TABLE_NONE) {
815 			cur = filt->pft_table;
816 			if (cur == PCIDB_TABLE_VENDOR)
817 				cur = PCIDB_TABLE_DEVICE;
818 			continue;
819 		}
820 
821 		if (pcidb_table_to_group(cur) != filt->pft_tgrp) {
822 			errx(EXIT_FAILURE, "filter %s targets the %s table, "
823 			    "but other filters target the %s group of tables: "
824 			    "both cannot be used at the same time",
825 			    filt->pft_raw,
826 			    pcidb_table_to_string(filt->pft_table),
827 			    pcidb_table_to_grpstr(cur));
828 		}
829 
830 		/*
831 		 * This confirms the current filter is less than the target. The
832 		 * equality case was handled up above.
833 		 */
834 		if (filt->pft_table < cur) {
835 			continue;
836 		}
837 
838 		/*
839 		 * We require a more specific table than we currently have. If
840 		 * this hasn't been requested, then it's fine. Otherwise it's an
841 		 * error. We can't change an explicitly requested table out from
842 		 * a user.
843 		 */
844 		if (tset) {
845 			errx(EXIT_FAILURE, "filter %s needs to match against "
846 			    "table %s, which is more specific than the "
847 			    "requested table %s", filt->pft_raw,
848 			    pcidb_table_to_string(filt->pft_table),
849 			    pcidb_table_to_string(cur));
850 		}
851 		cur = filt->pft_table;
852 	}
853 
854 	*table = cur;
855 }
856 
857 static void
pcidb_drop_privs(void)858 pcidb_drop_privs(void)
859 {
860 	priv_set_t *curprivs, *targprivs;
861 
862 	if ((curprivs = priv_allocset()) == NULL) {
863 		err(EXIT_FAILURE, "failed to allocate privilege set to drop "
864 		    "privs");
865 	}
866 
867 	if (getppriv(PRIV_EFFECTIVE, curprivs) != 0) {
868 		err(EXIT_FAILURE, "failed to get current privileges");
869 	}
870 
871 	if ((targprivs = priv_allocset()) == NULL) {
872 		err(EXIT_FAILURE, "failed to allocate privilege set to drop "
873 		    "privs");
874 	}
875 
876 	/*
877 	 * Set our privileges to the minimum required. Because stdout will have
878 	 * already been opened, all we need is the ability to read files from
879 	 * basic privileges. We opt to keep FILE_DAC_READ if the caller has it
880 	 * just in case there is something weird about the location of the
881 	 * pci.ids files.
882 	 */
883 	priv_basicset(targprivs);
884 	VERIFY0(priv_delset(targprivs, PRIV_FILE_LINK_ANY));
885 	VERIFY0(priv_delset(targprivs, PRIV_PROC_INFO));
886 	VERIFY0(priv_delset(targprivs, PRIV_PROC_SESSION));
887 	VERIFY0(priv_delset(targprivs, PRIV_PROC_FORK));
888 	VERIFY0(priv_delset(targprivs, PRIV_NET_ACCESS));
889 	VERIFY0(priv_delset(targprivs, PRIV_FILE_WRITE));
890 	VERIFY0(priv_delset(targprivs, PRIV_PROC_EXEC));
891 	VERIFY0(priv_addset(targprivs, PRIV_FILE_DAC_READ));
892 
893 	priv_intersect(curprivs, targprivs);
894 
895 	if (setppriv(PRIV_SET, PRIV_EFFECTIVE, targprivs) != 0) {
896 		err(EXIT_FAILURE, "failed to reduce privileges");
897 	}
898 
899 	priv_freeset(curprivs);
900 	priv_freeset(targprivs);
901 }
902 
903 static int
pcidb_usage(const char * fmt,...)904 pcidb_usage(const char *fmt, ...)
905 {
906 	if (fmt != NULL) {
907 		va_list ap;
908 
909 		(void) fprintf(stderr, "%s: ", pcidb_progname);
910 		va_start(ap, fmt);
911 		(void) vfprintf(stderr, fmt, ap);
912 		va_end(ap);
913 		(void) fprintf(stderr, "\n");
914 	}
915 
916 	(void) fprintf(stderr, "usage:  %s [-v|-d|-s|-c|-S|-i] [-H]"
917 	    "[[-p] [-o <field>[,...]] [<filter>]\n\n"
918 	    "\t-v\t\tshow vendor table\n"
919 	    "\t-d\t\tshow device table\n"
920 	    "\t-s\t\tshow subsystem table\n"
921 	    "\t-c\t\tshow class table\n"
922 	    "\t-S\t\tshow subclass table\n"
923 	    "\t-i\t\tshow programming interface table\n"
924 	    "\t-H\t\tdo not output column headers\n"
925 	    "\t-p\t\toutput in parsable form\n"
926 	    "\t-o field\toutput only specified fields\n\n"
927 	    "filters take the form of PCI aliases, e.g. pci8086,1522, "
928 	    "pci1028,1f44,s, or\n"
929 	    "pciex1022,1480.1462,7c37. Classes can be specified in a similar "
930 	    "way, e.g.\npciclass,010802 or pciclass,0403.\n\n"
931 	    "If no table is specified, then a table will be picked based on "
932 	    "the specified\nfilters. The default is to use the device table.\n",
933 	    pcidb_progname);
934 
935 	return (EXIT_USAGE);
936 }
937 
938 int
main(int argc,char * argv[])939 main(int argc, char *argv[])
940 {
941 	pcidb_hdl_t *hdl;
942 	int c, ret;
943 	uint_t tablecnt = 0;
944 	pcidb_table_t table = PCIDB_TABLE_NONE;
945 	boolean_t parse = B_FALSE, strcase = B_FALSE;
946 	const char *fields = NULL;
947 	const char *ofmt_fields_str = NULL;
948 	const ofmt_field_t *ofmt_fields = NULL;
949 	ofmt_handle_t ofmt;
950 	ofmt_status_t oferr;
951 	uint_t flags = 0;
952 	pcidb_walk_t walk;
953 
954 	bzero(&walk, sizeof (walk));
955 	pcidb_progname = basename(argv[0]);
956 
957 	pcidb_drop_privs();
958 
959 	while ((c = getopt(argc, argv, ":vdscSipo:hH")) != -1) {
960 		switch (c) {
961 		case 'v':
962 			tablecnt++;
963 			table = PCIDB_TABLE_VENDOR;
964 			break;
965 		case 'd':
966 			tablecnt++;
967 			table = PCIDB_TABLE_DEVICE;
968 			break;
969 		case 's':
970 			tablecnt++;
971 			table = PCIDB_TABLE_SUBSYSTEM;
972 			break;
973 		case 'c':
974 			tablecnt++;
975 			table = PCIDB_TABLE_CLASS;
976 			break;
977 		case 'S':
978 			tablecnt++;
979 			table = PCIDB_TABLE_SUBCLASS;
980 			break;
981 		case 'i':
982 			tablecnt++;
983 			table = PCIDB_TABLE_PROGIF;
984 			break;
985 		case 'p':
986 			parse = B_TRUE;
987 			flags |= OFMT_PARSABLE;
988 			break;
989 		case 'o':
990 			fields = optarg;
991 			break;
992 		case 'h':
993 			return (pcidb_usage(NULL));
994 		case 'H':
995 			flags |= OFMT_NOHEADER;
996 			break;
997 		case ':':
998 			return (pcidb_usage("Option -%c requires an argument",
999 			    optopt));
1000 		case '?':
1001 			return (pcidb_usage("unknown option: -%c", optopt));
1002 		}
1003 	}
1004 
1005 	if (tablecnt > 1) {
1006 		errx(EXIT_USAGE, "more than one table specified, only one of "
1007 		    "-v, -d, -s, -c, -S, and -i may be specified");
1008 	}
1009 
1010 	if (parse && fields == NULL) {
1011 		errx(EXIT_USAGE, "-p requires fields specified with -o");
1012 	}
1013 
1014 	argc -= optind;
1015 	argv += optind;
1016 
1017 	/*
1018 	 * Determine that the set of filters we have been asked for makes sense
1019 	 * with the table that's been requested. If no table has been requested,
1020 	 * then go ahead and adjust our table to this. If there's still no table
1021 	 * that's been asked for because there are no filters, then we just go
1022 	 * to the default of the device table.
1023 	 */
1024 	pcidb_parse_filters(argc, argv, &walk);
1025 	pcidb_validate_filters(&walk, &table);
1026 
1027 	switch (table) {
1028 	case PCIDB_TABLE_VENDOR:
1029 		ofmt_fields = pcidb_vendor_ofmt;
1030 		ofmt_fields_str = pcidb_vendor_fields;
1031 		break;
1032 	case PCIDB_TABLE_NONE:
1033 	case PCIDB_TABLE_DEVICE:
1034 		ofmt_fields = pcidb_device_ofmt;
1035 		ofmt_fields_str = pcidb_device_fields;
1036 		break;
1037 	case PCIDB_TABLE_SUBSYSTEM:
1038 		ofmt_fields = pcidb_subsystem_ofmt;
1039 		ofmt_fields_str = pcidb_subsystem_fields;
1040 		break;
1041 	case PCIDB_TABLE_CLASS:
1042 		ofmt_fields = pcidb_class_ofmt;
1043 		ofmt_fields_str = pcidb_class_fields;
1044 		break;
1045 	case PCIDB_TABLE_SUBCLASS:
1046 		ofmt_fields = pcidb_subclass_ofmt;
1047 		ofmt_fields_str = pcidb_subclass_fields;
1048 		break;
1049 	case PCIDB_TABLE_PROGIF:
1050 		ofmt_fields = pcidb_progif_ofmt;
1051 		ofmt_fields_str = pcidb_progif_fields;
1052 		break;
1053 	}
1054 
1055 	if (fields == NULL) {
1056 		fields = ofmt_fields_str;
1057 	}
1058 
1059 	oferr = ofmt_open(fields, ofmt_fields, flags, 0, &ofmt);
1060 	ofmt_check(oferr, parse, ofmt, pcidb_ofmt_errx, warnx);
1061 
1062 	hdl = pcidb_open(PCIDB_VERSION);
1063 	if (hdl == NULL) {
1064 		err(EXIT_FAILURE, "failed to initialize PCI IDs database");
1065 	}
1066 
1067 	walk.pw_hdl = hdl;
1068 	walk.pw_ofmt = ofmt;
1069 	walk.pw_strcase = strcase;
1070 
1071 	switch (table) {
1072 	case PCIDB_TABLE_VENDOR:
1073 		pcidb_walk_vendors(&walk);
1074 		break;
1075 	case PCIDB_TABLE_NONE:
1076 	case PCIDB_TABLE_DEVICE:
1077 		pcidb_walk_devices(&walk);
1078 		break;
1079 	case PCIDB_TABLE_SUBSYSTEM:
1080 		pcidb_walk_subsystems(&walk);
1081 		break;
1082 	case PCIDB_TABLE_CLASS:
1083 		pcidb_walk_classes(&walk);
1084 		break;
1085 	case PCIDB_TABLE_SUBCLASS:
1086 		pcidb_walk_subclasses(&walk);
1087 		break;
1088 	case PCIDB_TABLE_PROGIF:
1089 		pcidb_walk_progifs(&walk);
1090 		break;
1091 	}
1092 
1093 	ofmt_close(ofmt);
1094 	pcidb_close(hdl);
1095 
1096 	/*
1097 	 * Check that all filters were used. We don't bother with checking if we
1098 	 * printed anything more broadly because we know that the database will
1099 	 * always have something in there so no filters should always print
1100 	 * something.
1101 	 */
1102 	ret = EXIT_SUCCESS;
1103 	for (uint_t i = 0; i < walk.pw_nfilters; i++) {
1104 		if (!walk.pw_filters[i].pft_used) {
1105 			warnx("filter '%s' did not match anything",
1106 			    walk.pw_filters[i].pft_raw);
1107 			ret = EXIT_FAILURE;
1108 		}
1109 	}
1110 
1111 	return (ret);
1112 }
1113