xref: /illumos-gate/usr/src/cmd/pcidb/pcidb.c (revision 59c22e7a2e5efed7b82bdb64375da8627629d3e9)
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 2021 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_OFMT_VID,
56 	PCIDB_OFMT_VENSTR,
57 	PCIDB_OFMT_DID,
58 	PCIDB_OFMT_DEVSTR,
59 	PCIDB_OFMT_SVID,
60 	PCIDB_OFMT_SDID,
61 	PCIDB_OFMT_SUBVENSTR,
62 	PCIDB_OFMT_SUBSYSSTR,
63 	PCIDB_OFMT_BCC,
64 	PCIDB_OFMT_CLASSSTR,
65 	PCIDB_OFMT_SCC,
66 	PCIDB_OFMT_SUBCLASSSTR,
67 	PCIDB_OFMT_PI,
68 	PCIDB_OFMT_PROGIFSTR
69 } pcidb_ofmt_t;
70 
71 typedef struct pcidb_filter {
72 	uint32_t pft_vend;
73 	uint32_t pft_dev;
74 	uint32_t pft_subven;
75 	uint32_t pft_subdev;
76 	uint32_t pft_class;
77 	uint32_t pft_subclass;
78 	uint32_t pft_progif;
79 } pcidb_filter_t;
80 
81 #define	PCIDB_NOFILTER	UINT32_MAX
82 
83 typedef struct pcidb_walk {
84 	pcidb_hdl_t *pw_hdl;
85 	ofmt_handle_t pw_ofmt;
86 	pcidb_vendor_t *pw_vendor;
87 	pcidb_device_t *pw_device;
88 	pcidb_subvd_t *pw_subvd;
89 	pcidb_class_t *pw_class;
90 	pcidb_subclass_t *pw_subclass;
91 	pcidb_progif_t *pw_progif;
92 	boolean_t pw_strcase;
93 	uint_t pw_nfilters;
94 	pcidb_filter_t *pw_filters;
95 } pcidb_walk_t;
96 
97 static boolean_t
98 pcidb_write_vendor(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
99 {
100 	pcidb_walk_t *walk = ofarg->ofmt_cbarg;
101 
102 	VERIFY(walk->pw_vendor != NULL);
103 	switch (ofarg->ofmt_id) {
104 	case PCIDB_OFMT_VID:
105 		(void) snprintf(buf, buflen, "%x",
106 		    pcidb_vendor_id(walk->pw_vendor));
107 		break;
108 	case PCIDB_OFMT_VENSTR:
109 		(void) strlcpy(buf, pcidb_vendor_name(walk->pw_vendor), buflen);
110 		break;
111 	default:
112 		abort();
113 	}
114 	return (B_TRUE);
115 }
116 
117 static boolean_t
118 pcidb_write_device(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
119 {
120 	pcidb_walk_t *walk = ofarg->ofmt_cbarg;
121 
122 	VERIFY(walk->pw_device != NULL);
123 	switch (ofarg->ofmt_id) {
124 	case PCIDB_OFMT_DID:
125 		(void) snprintf(buf, buflen, "%x",
126 		    pcidb_device_id(walk->pw_device));
127 		break;
128 	case PCIDB_OFMT_DEVSTR:
129 		(void) strlcpy(buf, pcidb_device_name(walk->pw_device), buflen);
130 		break;
131 	default:
132 		abort();
133 	}
134 	return (B_TRUE);
135 }
136 
137 static boolean_t
138 pcidb_write_subsystem(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
139 {
140 	pcidb_walk_t *walk = ofarg->ofmt_cbarg;
141 	pcidb_vendor_t *vendor;
142 
143 	VERIFY(walk->pw_subvd != NULL);
144 	switch (ofarg->ofmt_id) {
145 	case PCIDB_OFMT_SVID:
146 		(void) snprintf(buf, buflen, "%x",
147 		    pcidb_subvd_svid(walk->pw_subvd));
148 		break;
149 	case PCIDB_OFMT_SDID:
150 		(void) snprintf(buf, buflen, "%x",
151 		    pcidb_subvd_sdid(walk->pw_subvd));
152 		break;
153 	case PCIDB_OFMT_SUBSYSSTR:
154 		(void) strlcpy(buf, pcidb_subvd_name(walk->pw_subvd), buflen);
155 		break;
156 	case PCIDB_OFMT_SUBVENSTR:
157 		vendor = pcidb_lookup_vendor(walk->pw_hdl,
158 		    pcidb_subvd_svid(walk->pw_subvd));
159 		if (vendor == NULL) {
160 			return (B_FALSE);
161 		}
162 		(void) strlcpy(buf, pcidb_vendor_name(vendor), buflen);
163 		break;
164 	default:
165 		abort();
166 	}
167 	return (B_TRUE);
168 }
169 
170 static boolean_t
171 pcidb_write_class(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
172 {
173 	pcidb_walk_t *walk = ofarg->ofmt_cbarg;
174 
175 	VERIFY(walk->pw_class != NULL);
176 	switch (ofarg->ofmt_id) {
177 	case PCIDB_OFMT_BCC:
178 		(void) snprintf(buf, buflen, "%x",
179 		    pcidb_class_code(walk->pw_class));
180 		break;
181 	case PCIDB_OFMT_CLASSSTR:
182 		(void) strlcpy(buf, pcidb_class_name(walk->pw_class), buflen);
183 		break;
184 	default:
185 		abort();
186 	}
187 	return (B_TRUE);
188 }
189 
190 static boolean_t
191 pcidb_write_subclass(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
192 {
193 	pcidb_walk_t *walk = ofarg->ofmt_cbarg;
194 
195 	VERIFY(walk->pw_subclass != NULL);
196 	switch (ofarg->ofmt_id) {
197 	case PCIDB_OFMT_SCC:
198 		(void) snprintf(buf, buflen, "%x",
199 		    pcidb_subclass_code(walk->pw_subclass));
200 		break;
201 	case PCIDB_OFMT_SUBCLASSSTR:
202 		(void) strlcpy(buf, pcidb_subclass_name(walk->pw_subclass),
203 		    buflen);
204 		break;
205 	default:
206 		abort();
207 	}
208 	return (B_TRUE);
209 }
210 
211 static boolean_t
212 pcidb_write_progif(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
213 {
214 	pcidb_walk_t *walk = ofarg->ofmt_cbarg;
215 
216 	VERIFY(walk->pw_progif != NULL);
217 	switch (ofarg->ofmt_id) {
218 	case PCIDB_OFMT_PI:
219 		(void) snprintf(buf, buflen, "%x",
220 		    pcidb_progif_code(walk->pw_progif));
221 		break;
222 	case PCIDB_OFMT_PROGIFSTR:
223 		(void) strlcpy(buf, pcidb_progif_name(walk->pw_progif),
224 		    buflen);
225 		break;
226 	default:
227 		abort();
228 	}
229 	return (B_TRUE);
230 }
231 
232 static const char *pcidb_vendor_fields = "vid,vendor";
233 static const ofmt_field_t pcidb_vendor_ofmt[] = {
234 	{ "VID",	8,	PCIDB_OFMT_VID,		pcidb_write_vendor },
235 	{ "VENDOR",	30,	PCIDB_OFMT_VENSTR,	pcidb_write_vendor },
236 	{ NULL, 0, 0, NULL }
237 };
238 
239 static const char *pcidb_device_fields = "vid,did,vendor,device";
240 static const ofmt_field_t pcidb_device_ofmt[] = {
241 	{ "VID",	8,	PCIDB_OFMT_VID,		pcidb_write_vendor },
242 	{ "VENDOR",	30,	PCIDB_OFMT_VENSTR,	pcidb_write_vendor },
243 	{ "DID",	8,	PCIDB_OFMT_DID,		pcidb_write_device },
244 	{ "DEVICE",	30,	PCIDB_OFMT_DEVSTR,	pcidb_write_device },
245 	{ NULL, 0, 0, NULL }
246 };
247 
248 static const char *pcidb_subsystem_fields = "vid,did,svid,sdid,subsystem";
249 static const ofmt_field_t pcidb_subsystem_ofmt[] = {
250 	{ "VID",	8,	PCIDB_OFMT_VID,		pcidb_write_vendor },
251 	{ "VENDOR",	30,	PCIDB_OFMT_VENSTR,	pcidb_write_vendor },
252 	{ "DID",	8,	PCIDB_OFMT_DID,		pcidb_write_device },
253 	{ "DEVICE",	30,	PCIDB_OFMT_DEVSTR,	pcidb_write_device },
254 	{ "SVID",	8,	PCIDB_OFMT_SVID,	pcidb_write_subsystem },
255 	{ "SDID",	8,	PCIDB_OFMT_SDID,	pcidb_write_subsystem },
256 	{ "SUBSYSTEM",	30,	PCIDB_OFMT_SUBSYSSTR,	pcidb_write_subsystem },
257 	{ "SUBVENDOR",	30,	PCIDB_OFMT_SUBVENSTR,	pcidb_write_subsystem },
258 	{ NULL, 0, 0, NULL }
259 };
260 
261 static const char *pcidb_class_fields = "bcc,class";
262 static const ofmt_field_t pcidb_class_ofmt[] = {
263 	{ "BCC",	6,	PCIDB_OFMT_BCC,		pcidb_write_class },
264 	{ "CLASS",	30,	PCIDB_OFMT_CLASSSTR,	pcidb_write_class },
265 	{ NULL, 0, 0, NULL }
266 };
267 
268 static const char *pcidb_subclass_fields = "bcc,scc,class,subclass";
269 static const ofmt_field_t pcidb_subclass_ofmt[] = {
270 	{ "BCC",	6,	PCIDB_OFMT_BCC,		pcidb_write_class },
271 	{ "CLASS",	30,	PCIDB_OFMT_CLASSSTR,	pcidb_write_class },
272 	{ "SCC",	6,	PCIDB_OFMT_SCC,		pcidb_write_subclass },
273 	{ "SUBCLASS",	30,	PCIDB_OFMT_SUBCLASSSTR,	pcidb_write_subclass },
274 	{ NULL, 0, 0, NULL }
275 };
276 
277 static const char *pcidb_progif_fields = "bcc,scc,pi,subclass,interface";
278 static const ofmt_field_t pcidb_progif_ofmt[] = {
279 	{ "BCC",	6,	PCIDB_OFMT_BCC,		pcidb_write_class },
280 	{ "CLASS",	30,	PCIDB_OFMT_CLASSSTR,	pcidb_write_class },
281 	{ "SCC",	6,	PCIDB_OFMT_SCC,		pcidb_write_subclass },
282 	{ "SUBCLASS",	30,	PCIDB_OFMT_SUBCLASSSTR,	pcidb_write_subclass },
283 	{ "PI",		6,	PCIDB_OFMT_PI,		pcidb_write_progif },
284 	{ "INTERFACE",	30,	PCIDB_OFMT_PROGIFSTR,	pcidb_write_progif },
285 	{ NULL, 0, 0, NULL }
286 };
287 
288 static void
289 pcidb_ofmt_errx(const char *fmt, ...)
290 {
291 	va_list ap;
292 
293 	va_start(ap, fmt);
294 	verrx(EXIT_FAILURE, fmt, ap);
295 }
296 
297 static boolean_t
298 pcidb_filter_match(pcidb_walk_t *walk)
299 {
300 	if (walk->pw_nfilters == 0) {
301 		return (B_TRUE);
302 	}
303 
304 	for (uint_t i = 0; i < walk->pw_nfilters; i++) {
305 		const pcidb_filter_t *filt = &walk->pw_filters[i];
306 		if (filt->pft_vend != PCIDB_NOFILTER &&
307 		    (walk->pw_vendor == NULL ||
308 		    filt->pft_vend != pcidb_vendor_id(walk->pw_vendor))) {
309 			continue;
310 		}
311 
312 		if (filt->pft_dev != PCIDB_NOFILTER &&
313 		    (walk->pw_device == NULL ||
314 		    filt->pft_dev != pcidb_device_id(walk->pw_device))) {
315 			continue;
316 		}
317 
318 		if (filt->pft_subven != PCIDB_NOFILTER &&
319 		    (walk->pw_subvd == NULL ||
320 		    filt->pft_subven != pcidb_subvd_svid(walk->pw_subvd))) {
321 			continue;
322 		}
323 
324 		if (filt->pft_subdev != PCIDB_NOFILTER &&
325 		    (walk->pw_subvd == NULL ||
326 		    filt->pft_subdev != pcidb_subvd_sdid(walk->pw_subvd))) {
327 			continue;
328 		}
329 
330 		if (filt->pft_class != PCIDB_NOFILTER &&
331 		    (walk->pw_class == NULL ||
332 		    filt->pft_class != pcidb_class_code(walk->pw_class))) {
333 			continue;
334 		}
335 
336 		if (filt->pft_subclass != PCIDB_NOFILTER &&
337 		    (walk->pw_subclass == NULL ||
338 		    filt->pft_subclass !=
339 		    pcidb_subclass_code(walk->pw_subclass))) {
340 			continue;
341 		}
342 
343 		if (filt->pft_progif != PCIDB_NOFILTER &&
344 		    (walk->pw_progif == NULL ||
345 		    filt->pft_progif != pcidb_progif_code(walk->pw_progif))) {
346 			continue;
347 		}
348 
349 		return (B_TRUE);
350 	}
351 
352 	return (B_FALSE);
353 }
354 
355 static void
356 pcidb_walk_vendors(pcidb_walk_t *walk)
357 {
358 	pcidb_hdl_t *hdl = walk->pw_hdl;
359 
360 	for (pcidb_vendor_t *vend = pcidb_vendor_iter(hdl); vend != NULL;
361 	    vend = pcidb_vendor_iter_next(vend)) {
362 		walk->pw_vendor = vend;
363 		if (!pcidb_filter_match(walk))
364 			continue;
365 		ofmt_print(walk->pw_ofmt, walk);
366 	}
367 }
368 
369 static void
370 pcidb_walk_devices(pcidb_walk_t *walk)
371 {
372 	pcidb_hdl_t *hdl = walk->pw_hdl;
373 
374 	for (pcidb_vendor_t *vend = pcidb_vendor_iter(hdl); vend != NULL;
375 	    vend = pcidb_vendor_iter_next(vend)) {
376 		walk->pw_vendor = vend;
377 		for (pcidb_device_t *dev = pcidb_device_iter(vend); dev != NULL;
378 		    dev = pcidb_device_iter_next(dev)) {
379 			walk->pw_device = dev;
380 			if (!pcidb_filter_match(walk))
381 				continue;
382 			ofmt_print(walk->pw_ofmt, walk);
383 		}
384 	}
385 }
386 
387 static void
388 pcidb_walk_subsystems(pcidb_walk_t *walk)
389 {
390 	pcidb_hdl_t *hdl = walk->pw_hdl;
391 
392 	for (pcidb_vendor_t *vend = pcidb_vendor_iter(hdl); vend != NULL;
393 	    vend = pcidb_vendor_iter_next(vend)) {
394 		walk->pw_vendor = vend;
395 		for (pcidb_device_t *dev = pcidb_device_iter(vend); dev != NULL;
396 		    dev = pcidb_device_iter_next(dev)) {
397 			walk->pw_device = dev;
398 			for (pcidb_subvd_t *sub = pcidb_subvd_iter(dev);
399 			    sub != NULL; sub = pcidb_subvd_iter_next(sub)) {
400 				walk->pw_subvd = sub;
401 				if (!pcidb_filter_match(walk))
402 					continue;
403 				ofmt_print(walk->pw_ofmt, walk);
404 			}
405 		}
406 
407 	}
408 }
409 
410 static void
411 pcidb_walk_classes(pcidb_walk_t *walk)
412 {
413 	for (pcidb_class_t *class = pcidb_class_iter(walk->pw_hdl);
414 	    class != NULL; class = pcidb_class_iter_next(class)) {
415 		walk->pw_class = class;
416 		if (!pcidb_filter_match(walk))
417 			continue;
418 		ofmt_print(walk->pw_ofmt, walk);
419 	}
420 }
421 
422 static void
423 pcidb_walk_subclasses(pcidb_walk_t *walk)
424 {
425 	for (pcidb_class_t *class = pcidb_class_iter(walk->pw_hdl);
426 	    class != NULL; class = pcidb_class_iter_next(class)) {
427 		walk->pw_class = class;
428 		for (pcidb_subclass_t *sub = pcidb_subclass_iter(class);
429 		    sub != NULL; sub = pcidb_subclass_iter_next(sub)) {
430 			walk->pw_subclass = sub;
431 			if (!pcidb_filter_match(walk))
432 				continue;
433 			ofmt_print(walk->pw_ofmt, walk);
434 		}
435 	}
436 }
437 
438 static void
439 pcidb_walk_progifs(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 		for (pcidb_subclass_t *sub = pcidb_subclass_iter(class);
445 		    sub != NULL; sub = pcidb_subclass_iter_next(sub)) {
446 			walk->pw_subclass = sub;
447 			for (pcidb_progif_t *progif = pcidb_progif_iter(sub);
448 			    progif != NULL;
449 			    progif = pcidb_progif_iter_next(progif)) {
450 				walk->pw_progif = progif;
451 				if (!pcidb_filter_match(walk))
452 					continue;
453 				ofmt_print(walk->pw_ofmt, walk);
454 			}
455 		}
456 	}
457 }
458 
459 static void
460 pcidb_parse_class_filter(pcidb_filter_t *filter, char *arg, const char *orig)
461 {
462 	size_t len;
463 	unsigned long val;
464 	char *eptr;
465 
466 	filter->pft_vend = filter->pft_dev = PCIDB_NOFILTER;
467 	filter->pft_subven = filter->pft_subdev = PCIDB_NOFILTER;
468 
469 	len = strlen(arg);
470 	if (len != 2 && len != 4 && len != 6) {
471 		errx(EXIT_FAILURE, "invalid class filter: '%s': bad length",
472 		    orig);
473 	}
474 
475 	errno = 0;
476 	val = strtoul(arg, &eptr, 16);
477 	if (errno != 0 || *eptr != '\0') {
478 		errx(EXIT_FAILURE, "invalid class filter: '%s': failed to "
479 		    "parse hex string", orig);
480 	}
481 
482 	if (len == 6) {
483 		filter->pft_progif = val & 0xff;
484 		val = val >> 8;
485 	} else {
486 		filter->pft_progif = PCIDB_NOFILTER;
487 	}
488 
489 	if (len >= 4) {
490 		filter->pft_subclass = val & 0xff;
491 		val = val >> 8;
492 	} else {
493 		filter->pft_subclass = PCIDB_NOFILTER;
494 	}
495 
496 	filter->pft_class = val & 0xff;
497 }
498 
499 static void
500 pcidb_parse_device_filter(pcidb_filter_t *filter, char *arg, const char *orig)
501 {
502 	unsigned long val;
503 	uint32_t primary, secondary;
504 	char *eptr;
505 
506 	filter->pft_vend = filter->pft_dev = PCIDB_NOFILTER;
507 	filter->pft_subven = filter->pft_subdev = PCIDB_NOFILTER;
508 	filter->pft_class = filter->pft_subclass = PCIDB_NOFILTER;
509 	filter->pft_progif = PCIDB_NOFILTER;
510 
511 	errno = 0;
512 	val = strtoul(arg, &eptr, 16);
513 	if (errno != 0 || (*eptr != '\0' && *eptr != ',')) {
514 		errx(EXIT_FAILURE, "invalid device filter: '%s': failed to "
515 		    "parse hex string", orig);
516 	}
517 
518 	if (val > UINT16_MAX) {
519 		errx(EXIT_FAILURE, "invalid id: %x is larger than 0xffff", val);
520 	}
521 
522 	primary = (uint32_t)val;
523 	if (*eptr == '\0') {
524 		filter->pft_vend = primary;
525 		return;
526 	} else if (strcmp(eptr, ",s") == 0) {
527 		filter->pft_subven = primary;
528 		return;
529 	} else if (eptr[1] == '\0') {
530 		errx(EXIT_FAILURE, "invalid device filter: '%s': filter "
531 		    "terminated early", arg);
532 	}
533 
534 	arg = eptr + 1;
535 	val = strtoul(arg, &eptr, 16);
536 	if (errno != 0 || (*eptr != '\0' && *eptr != ',' && *eptr != '.')) {
537 		errx(EXIT_FAILURE, "invalid device filter: '%s': failed to "
538 		    "parse hex string at %s", orig, arg);
539 	}
540 
541 	if (val > UINT16_MAX) {
542 		errx(EXIT_FAILURE, "invalid id: %x is larger than 0xffff", val);
543 	}
544 
545 	secondary = (uint32_t)val;
546 	if (*eptr == '\0') {
547 		filter->pft_vend = primary;
548 		filter->pft_dev = secondary;
549 		return;
550 	} else if (eptr[1] == '\0') {
551 		errx(EXIT_FAILURE, "invalid device filter: '%s': filter "
552 		    "terminated early", arg);
553 	}
554 
555 	if (*eptr == ',') {
556 		if (eptr[1] == 'p' && eptr[2] == '\0') {
557 			filter->pft_vend = primary;
558 			filter->pft_dev = secondary;
559 			return;
560 		}
561 		if (eptr[1] == 's' && eptr[2] == '\0') {
562 			filter->pft_subven = primary;
563 			filter->pft_subdev = secondary;
564 			return;
565 		}
566 		errx(EXIT_FAILURE, "invalid device filter: '%s': invalid "
567 		    "trailing comma at %s, expected either ,p or ,s",
568 		    orig, eptr);
569 	}
570 
571 	filter->pft_vend = primary;
572 	filter->pft_dev = secondary;
573 
574 	arg = eptr + 1;
575 	errno = 0;
576 	val = strtoul(arg, &eptr, 16);
577 	if (errno != 0 || (*eptr != '\0' && *eptr != ',')) {
578 		errx(EXIT_FAILURE, "invalid device filter: '%s': failed to "
579 		    "parse hex string at %s", orig, arg);
580 	}
581 
582 	if (val > UINT16_MAX) {
583 		errx(EXIT_FAILURE, "invalid id: %x is larger than 0xffff", val);
584 	}
585 
586 	filter->pft_subven = (uint32_t)val;
587 	if (*eptr == '\0') {
588 		return;
589 	} else if (eptr[1] == '\0') {
590 		errx(EXIT_FAILURE, "invalid device filter: '%s': filter "
591 		    "terminated early", arg);
592 	}
593 
594 	arg = eptr + 1;
595 	errno = 0;
596 	val = strtoul(arg, &eptr, 16);
597 	if (errno != 0 || *eptr != '\0') {
598 		errx(EXIT_FAILURE, "invalid device filter: '%s': failed to "
599 		    "parse hex string at %s", orig, arg);
600 	}
601 
602 	if (val > UINT16_MAX) {
603 		errx(EXIT_FAILURE, "invalid id: %x is larger than 0xffff", val);
604 	}
605 
606 	filter->pft_subdev = (uint32_t)val;
607 }
608 
609 
610 /*
611  * Process a series of alias style ways of indicating numeric filters. Use the
612  * basic alias format for now.
613  */
614 static void
615 pcidb_process_filters(int argc, char *argv[], pcidb_walk_t *walkp)
616 {
617 	if (argc <= 0) {
618 		walkp->pw_nfilters = 0;
619 		return;
620 	}
621 
622 	walkp->pw_nfilters = argc;
623 	walkp->pw_filters = calloc(walkp->pw_nfilters, sizeof (pcidb_filter_t));
624 	if (walkp->pw_filters == NULL) {
625 		err(EXIT_FAILURE, "failed to allocate memory for filters");
626 	}
627 
628 	for (int i = 0; i < argc; i++) {
629 		char *str = strdup(argv[i]);
630 
631 		if (str == NULL) {
632 			errx(EXIT_FAILURE, "failed to duplicate string %s",
633 			    argv[i]);
634 		}
635 
636 		if (strncmp(str, "pciexclass,", 11) == 0) {
637 			pcidb_parse_class_filter(&walkp->pw_filters[i],
638 			    str + 11, argv[i]);
639 		} else if (strncmp(str, "pciclass,", 9) == 0) {
640 			pcidb_parse_class_filter(&walkp->pw_filters[i], str + 9,
641 			    argv[i]);
642 		} else if (strncmp(str, "pciex", 5) == 0) {
643 			pcidb_parse_device_filter(&walkp->pw_filters[i],
644 			    str + 5, argv[i]);
645 		} else if (strncmp(str, "pci", 3) == 0) {
646 			pcidb_parse_device_filter(&walkp->pw_filters[i],
647 			    str + 3, argv[i]);
648 		} else {
649 			errx(EXIT_FAILURE, "invalid filter string: %s", str);
650 		}
651 
652 		free(str);
653 	}
654 }
655 
656 static void
657 pcidb_drop_privs(void)
658 {
659 	priv_set_t *curprivs, *targprivs;
660 
661 	if ((curprivs = priv_allocset()) == NULL) {
662 		err(EXIT_FAILURE, "failed to allocate privilege set to drop "
663 		    "privs");
664 	}
665 
666 	if (getppriv(PRIV_EFFECTIVE, curprivs) != 0) {
667 		err(EXIT_FAILURE, "failed to get current privileges");
668 	}
669 
670 	if ((targprivs = priv_allocset()) == NULL) {
671 		err(EXIT_FAILURE, "failed to allocate privilege set to drop "
672 		    "privs");
673 	}
674 
675 	/*
676 	 * Set our privileges to the minimum required. Because stdout will have
677 	 * already been opened, all we need is the ability to read files from
678 	 * basic privileges. We opt to keep FILE_DAC_READ if the caller has it
679 	 * just in case there is something weird about the location of the
680 	 * pci.ids files.
681 	 */
682 	priv_basicset(targprivs);
683 	VERIFY0(priv_delset(targprivs, PRIV_FILE_LINK_ANY));
684 	VERIFY0(priv_delset(targprivs, PRIV_PROC_INFO));
685 	VERIFY0(priv_delset(targprivs, PRIV_PROC_SESSION));
686 	VERIFY0(priv_delset(targprivs, PRIV_PROC_FORK));
687 	VERIFY0(priv_delset(targprivs, PRIV_NET_ACCESS));
688 	VERIFY0(priv_delset(targprivs, PRIV_FILE_WRITE));
689 	VERIFY0(priv_delset(targprivs, PRIV_PROC_EXEC));
690 	VERIFY0(priv_addset(targprivs, PRIV_FILE_DAC_READ));
691 
692 	priv_intersect(curprivs, targprivs);
693 
694 	if (setppriv(PRIV_SET, PRIV_EFFECTIVE, targprivs) != 0) {
695 		err(EXIT_FAILURE, "failed to reduce privileges");
696 	}
697 
698 	priv_freeset(curprivs);
699 	priv_freeset(targprivs);
700 }
701 
702 static int
703 pcidb_usage(const char *fmt, ...)
704 {
705 	if (fmt != NULL) {
706 		va_list ap;
707 
708 		(void) fprintf(stderr, "%s: ", pcidb_progname);
709 		va_start(ap, fmt);
710 		(void) vfprintf(stderr, fmt, ap);
711 		va_end(ap);
712 		(void) fprintf(stderr, "\n");
713 	}
714 
715 	(void) fprintf(stderr, "usage:  %s [-v|-d|-s|-c|-S|-i] [-H]"
716 	    "[[-p] [-o <field>[,...]] [<filter>]\n\n"
717 	    "\t-v\t\tshow vendor table\n"
718 	    "\t-d\t\tshow device table\n"
719 	    "\t-s\t\tshow subsystem table\n"
720 	    "\t-c\t\tshow class table\n"
721 	    "\t-S\t\tshow subclass table\n"
722 	    "\t-i\t\tshow programming interface table\n"
723 	    "\t-H\t\tdo not output column headers\n"
724 	    "\t-p\t\toutput in parsable form\n"
725 	    "\t-o field\toutput only specified fields\n\n"
726 	    "filters take the form of PCI aliases, e.g. pci8086,1522, "
727 	    "pci1028,1f44,s, or\n"
728 	    "pciex1022,1480.1462,7c37. Classes can be specified in a similar "
729 	    "way, e.g.\npciclass,010802 or pciclass,0403.\n", pcidb_progname);
730 
731 	return (EXIT_USAGE);
732 }
733 
734 int
735 main(int argc, char *argv[])
736 {
737 	pcidb_hdl_t *hdl;
738 	int c;
739 	uint_t tablecnt = 0;
740 	pcidb_table_t table = PCIDB_TABLE_NONE;
741 	boolean_t parse = B_FALSE, strcase = B_FALSE;
742 	const char *fields = NULL;
743 	const char *ofmt_fields_str = NULL;
744 	const ofmt_field_t *ofmt_fields = NULL;
745 	ofmt_handle_t ofmt;
746 	ofmt_status_t oferr;
747 	uint_t flags = 0;
748 	pcidb_walk_t walk;
749 
750 	bzero(&walk, sizeof (walk));
751 	pcidb_progname = basename(argv[0]);
752 
753 	pcidb_drop_privs();
754 
755 	while ((c = getopt(argc, argv, ":vdscSipo:hH")) != -1) {
756 		switch (c) {
757 		case 'v':
758 			tablecnt++;
759 			table = PCIDB_TABLE_VENDOR;
760 			break;
761 		case 'd':
762 			tablecnt++;
763 			table = PCIDB_TABLE_DEVICE;
764 			break;
765 		case 's':
766 			tablecnt++;
767 			table = PCIDB_TABLE_SUBSYSTEM;
768 			break;
769 		case 'c':
770 			tablecnt++;
771 			table = PCIDB_TABLE_CLASS;
772 			break;
773 		case 'S':
774 			tablecnt++;
775 			table = PCIDB_TABLE_SUBCLASS;
776 			break;
777 		case 'i':
778 			tablecnt++;
779 			table = PCIDB_TABLE_PROGIF;
780 			break;
781 		case 'p':
782 			parse = B_TRUE;
783 			flags |= OFMT_PARSABLE;
784 			break;
785 		case 'o':
786 			fields = optarg;
787 			break;
788 		case 'h':
789 			return (pcidb_usage(NULL));
790 		case 'H':
791 			flags |= OFMT_NOHEADER;
792 			break;
793 		case ':':
794 			return (pcidb_usage("Option -%c requires an argument",
795 			    optopt));
796 		case '?':
797 			return (pcidb_usage("unknown option: -%c", optopt));
798 		}
799 	}
800 
801 	if (tablecnt > 1) {
802 		errx(EXIT_USAGE, "more than one table specified, only one of "
803 		    "-v, -d, -s, -c, -S, and -i may be specified");
804 	}
805 
806 	if (parse && fields == NULL) {
807 		errx(EXIT_USAGE, "-p requires fields specified with -o");
808 	}
809 
810 	argc -= optind;
811 	argv += optind;
812 
813 	pcidb_process_filters(argc, argv, &walk);
814 
815 	switch (table) {
816 	case PCIDB_TABLE_VENDOR:
817 		ofmt_fields = pcidb_vendor_ofmt;
818 		ofmt_fields_str = pcidb_vendor_fields;
819 		break;
820 	case PCIDB_TABLE_NONE:
821 	case PCIDB_TABLE_DEVICE:
822 		ofmt_fields = pcidb_device_ofmt;
823 		ofmt_fields_str = pcidb_device_fields;
824 		break;
825 	case PCIDB_TABLE_SUBSYSTEM:
826 		ofmt_fields = pcidb_subsystem_ofmt;
827 		ofmt_fields_str = pcidb_subsystem_fields;
828 		break;
829 	case PCIDB_TABLE_CLASS:
830 		ofmt_fields = pcidb_class_ofmt;
831 		ofmt_fields_str = pcidb_class_fields;
832 		break;
833 	case PCIDB_TABLE_SUBCLASS:
834 		ofmt_fields = pcidb_subclass_ofmt;
835 		ofmt_fields_str = pcidb_subclass_fields;
836 		break;
837 	case PCIDB_TABLE_PROGIF:
838 		ofmt_fields = pcidb_progif_ofmt;
839 		ofmt_fields_str = pcidb_progif_fields;
840 		break;
841 	}
842 
843 	if (fields == NULL) {
844 		fields = ofmt_fields_str;
845 	}
846 
847 	oferr = ofmt_open(fields, ofmt_fields, flags, 0, &ofmt);
848 	ofmt_check(oferr, parse, ofmt, pcidb_ofmt_errx, warnx);
849 
850 	hdl = pcidb_open(PCIDB_VERSION);
851 	if (hdl == NULL) {
852 		err(EXIT_FAILURE, "failed to initialize PCI IDs database");
853 	}
854 
855 	walk.pw_hdl = hdl;
856 	walk.pw_ofmt = ofmt;
857 	walk.pw_strcase = strcase;
858 
859 	switch (table) {
860 	case PCIDB_TABLE_VENDOR:
861 		pcidb_walk_vendors(&walk);
862 		break;
863 	case PCIDB_TABLE_NONE:
864 	case PCIDB_TABLE_DEVICE:
865 		pcidb_walk_devices(&walk);
866 		break;
867 	case PCIDB_TABLE_SUBSYSTEM:
868 		pcidb_walk_subsystems(&walk);
869 		break;
870 	case PCIDB_TABLE_CLASS:
871 		pcidb_walk_classes(&walk);
872 		break;
873 	case PCIDB_TABLE_SUBCLASS:
874 		pcidb_walk_subclasses(&walk);
875 		break;
876 	case PCIDB_TABLE_PROGIF:
877 		pcidb_walk_progifs(&walk);
878 		break;
879 	}
880 
881 	ofmt_close(ofmt);
882 	pcidb_close(hdl);
883 	return (EXIT_SUCCESS);
884 }
885