xref: /illumos-gate/usr/src/cmd/pcidb/pcidb.c (revision b91bbe3818919523e866b0d622e2dd56874530a7)
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: %lx is larger than 0xffff",
520 		    val);
521 	}
522 
523 	primary = (uint32_t)val;
524 	if (*eptr == '\0') {
525 		filter->pft_vend = primary;
526 		return;
527 	} else if (strcmp(eptr, ",s") == 0) {
528 		filter->pft_subven = primary;
529 		return;
530 	} else if (eptr[1] == '\0') {
531 		errx(EXIT_FAILURE, "invalid device filter: '%s': filter "
532 		    "terminated early", arg);
533 	}
534 
535 	arg = eptr + 1;
536 	val = strtoul(arg, &eptr, 16);
537 	if (errno != 0 || (*eptr != '\0' && *eptr != ',' && *eptr != '.')) {
538 		errx(EXIT_FAILURE, "invalid device filter: '%s': failed to "
539 		    "parse hex string at %s", orig, arg);
540 	}
541 
542 	if (val > UINT16_MAX) {
543 		errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff",
544 		    val);
545 	}
546 
547 	secondary = (uint32_t)val;
548 	if (*eptr == '\0') {
549 		filter->pft_vend = primary;
550 		filter->pft_dev = secondary;
551 		return;
552 	} else if (eptr[1] == '\0') {
553 		errx(EXIT_FAILURE, "invalid device filter: '%s': filter "
554 		    "terminated early", arg);
555 	}
556 
557 	if (*eptr == ',') {
558 		if (eptr[1] == 'p' && eptr[2] == '\0') {
559 			filter->pft_vend = primary;
560 			filter->pft_dev = secondary;
561 			return;
562 		}
563 		if (eptr[1] == 's' && eptr[2] == '\0') {
564 			filter->pft_subven = primary;
565 			filter->pft_subdev = secondary;
566 			return;
567 		}
568 		errx(EXIT_FAILURE, "invalid device filter: '%s': invalid "
569 		    "trailing comma at %s, expected either ,p or ,s",
570 		    orig, eptr);
571 	}
572 
573 	filter->pft_vend = primary;
574 	filter->pft_dev = secondary;
575 
576 	arg = eptr + 1;
577 	errno = 0;
578 	val = strtoul(arg, &eptr, 16);
579 	if (errno != 0 || (*eptr != '\0' && *eptr != ',')) {
580 		errx(EXIT_FAILURE, "invalid device filter: '%s': failed to "
581 		    "parse hex string at %s", orig, arg);
582 	}
583 
584 	if (val > UINT16_MAX) {
585 		errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff",
586 		    val);
587 	}
588 
589 	filter->pft_subven = (uint32_t)val;
590 	if (*eptr == '\0') {
591 		return;
592 	} else if (eptr[1] == '\0') {
593 		errx(EXIT_FAILURE, "invalid device filter: '%s': filter "
594 		    "terminated early", arg);
595 	}
596 
597 	arg = eptr + 1;
598 	errno = 0;
599 	val = strtoul(arg, &eptr, 16);
600 	if (errno != 0 || *eptr != '\0') {
601 		errx(EXIT_FAILURE, "invalid device filter: '%s': failed to "
602 		    "parse hex string at %s", orig, arg);
603 	}
604 
605 	if (val > UINT16_MAX) {
606 		errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff",
607 		    val);
608 	}
609 
610 	filter->pft_subdev = (uint32_t)val;
611 }
612 
613 
614 /*
615  * Process a series of alias style ways of indicating numeric filters. Use the
616  * basic alias format for now.
617  */
618 static void
619 pcidb_process_filters(int argc, char *argv[], pcidb_walk_t *walkp)
620 {
621 	if (argc <= 0) {
622 		walkp->pw_nfilters = 0;
623 		return;
624 	}
625 
626 	walkp->pw_nfilters = argc;
627 	walkp->pw_filters = calloc(walkp->pw_nfilters, sizeof (pcidb_filter_t));
628 	if (walkp->pw_filters == NULL) {
629 		err(EXIT_FAILURE, "failed to allocate memory for filters");
630 	}
631 
632 	for (int i = 0; i < argc; i++) {
633 		char *str = strdup(argv[i]);
634 
635 		if (str == NULL) {
636 			errx(EXIT_FAILURE, "failed to duplicate string %s",
637 			    argv[i]);
638 		}
639 
640 		if (strncmp(str, "pciexclass,", 11) == 0) {
641 			pcidb_parse_class_filter(&walkp->pw_filters[i],
642 			    str + 11, argv[i]);
643 		} else if (strncmp(str, "pciclass,", 9) == 0) {
644 			pcidb_parse_class_filter(&walkp->pw_filters[i], str + 9,
645 			    argv[i]);
646 		} else if (strncmp(str, "pciex", 5) == 0) {
647 			pcidb_parse_device_filter(&walkp->pw_filters[i],
648 			    str + 5, argv[i]);
649 		} else if (strncmp(str, "pci", 3) == 0) {
650 			pcidb_parse_device_filter(&walkp->pw_filters[i],
651 			    str + 3, argv[i]);
652 		} else {
653 			errx(EXIT_FAILURE, "invalid filter string: %s", str);
654 		}
655 
656 		free(str);
657 	}
658 }
659 
660 static void
661 pcidb_drop_privs(void)
662 {
663 	priv_set_t *curprivs, *targprivs;
664 
665 	if ((curprivs = priv_allocset()) == NULL) {
666 		err(EXIT_FAILURE, "failed to allocate privilege set to drop "
667 		    "privs");
668 	}
669 
670 	if (getppriv(PRIV_EFFECTIVE, curprivs) != 0) {
671 		err(EXIT_FAILURE, "failed to get current privileges");
672 	}
673 
674 	if ((targprivs = priv_allocset()) == NULL) {
675 		err(EXIT_FAILURE, "failed to allocate privilege set to drop "
676 		    "privs");
677 	}
678 
679 	/*
680 	 * Set our privileges to the minimum required. Because stdout will have
681 	 * already been opened, all we need is the ability to read files from
682 	 * basic privileges. We opt to keep FILE_DAC_READ if the caller has it
683 	 * just in case there is something weird about the location of the
684 	 * pci.ids files.
685 	 */
686 	priv_basicset(targprivs);
687 	VERIFY0(priv_delset(targprivs, PRIV_FILE_LINK_ANY));
688 	VERIFY0(priv_delset(targprivs, PRIV_PROC_INFO));
689 	VERIFY0(priv_delset(targprivs, PRIV_PROC_SESSION));
690 	VERIFY0(priv_delset(targprivs, PRIV_PROC_FORK));
691 	VERIFY0(priv_delset(targprivs, PRIV_NET_ACCESS));
692 	VERIFY0(priv_delset(targprivs, PRIV_FILE_WRITE));
693 	VERIFY0(priv_delset(targprivs, PRIV_PROC_EXEC));
694 	VERIFY0(priv_addset(targprivs, PRIV_FILE_DAC_READ));
695 
696 	priv_intersect(curprivs, targprivs);
697 
698 	if (setppriv(PRIV_SET, PRIV_EFFECTIVE, targprivs) != 0) {
699 		err(EXIT_FAILURE, "failed to reduce privileges");
700 	}
701 
702 	priv_freeset(curprivs);
703 	priv_freeset(targprivs);
704 }
705 
706 static int
707 pcidb_usage(const char *fmt, ...)
708 {
709 	if (fmt != NULL) {
710 		va_list ap;
711 
712 		(void) fprintf(stderr, "%s: ", pcidb_progname);
713 		va_start(ap, fmt);
714 		(void) vfprintf(stderr, fmt, ap);
715 		va_end(ap);
716 		(void) fprintf(stderr, "\n");
717 	}
718 
719 	(void) fprintf(stderr, "usage:  %s [-v|-d|-s|-c|-S|-i] [-H]"
720 	    "[[-p] [-o <field>[,...]] [<filter>]\n\n"
721 	    "\t-v\t\tshow vendor table\n"
722 	    "\t-d\t\tshow device table\n"
723 	    "\t-s\t\tshow subsystem table\n"
724 	    "\t-c\t\tshow class table\n"
725 	    "\t-S\t\tshow subclass table\n"
726 	    "\t-i\t\tshow programming interface table\n"
727 	    "\t-H\t\tdo not output column headers\n"
728 	    "\t-p\t\toutput in parsable form\n"
729 	    "\t-o field\toutput only specified fields\n\n"
730 	    "filters take the form of PCI aliases, e.g. pci8086,1522, "
731 	    "pci1028,1f44,s, or\n"
732 	    "pciex1022,1480.1462,7c37. Classes can be specified in a similar "
733 	    "way, e.g.\npciclass,010802 or pciclass,0403.\n", pcidb_progname);
734 
735 	return (EXIT_USAGE);
736 }
737 
738 int
739 main(int argc, char *argv[])
740 {
741 	pcidb_hdl_t *hdl;
742 	int c;
743 	uint_t tablecnt = 0;
744 	pcidb_table_t table = PCIDB_TABLE_NONE;
745 	boolean_t parse = B_FALSE, strcase = B_FALSE;
746 	const char *fields = NULL;
747 	const char *ofmt_fields_str = NULL;
748 	const ofmt_field_t *ofmt_fields = NULL;
749 	ofmt_handle_t ofmt;
750 	ofmt_status_t oferr;
751 	uint_t flags = 0;
752 	pcidb_walk_t walk;
753 
754 	bzero(&walk, sizeof (walk));
755 	pcidb_progname = basename(argv[0]);
756 
757 	pcidb_drop_privs();
758 
759 	while ((c = getopt(argc, argv, ":vdscSipo:hH")) != -1) {
760 		switch (c) {
761 		case 'v':
762 			tablecnt++;
763 			table = PCIDB_TABLE_VENDOR;
764 			break;
765 		case 'd':
766 			tablecnt++;
767 			table = PCIDB_TABLE_DEVICE;
768 			break;
769 		case 's':
770 			tablecnt++;
771 			table = PCIDB_TABLE_SUBSYSTEM;
772 			break;
773 		case 'c':
774 			tablecnt++;
775 			table = PCIDB_TABLE_CLASS;
776 			break;
777 		case 'S':
778 			tablecnt++;
779 			table = PCIDB_TABLE_SUBCLASS;
780 			break;
781 		case 'i':
782 			tablecnt++;
783 			table = PCIDB_TABLE_PROGIF;
784 			break;
785 		case 'p':
786 			parse = B_TRUE;
787 			flags |= OFMT_PARSABLE;
788 			break;
789 		case 'o':
790 			fields = optarg;
791 			break;
792 		case 'h':
793 			return (pcidb_usage(NULL));
794 		case 'H':
795 			flags |= OFMT_NOHEADER;
796 			break;
797 		case ':':
798 			return (pcidb_usage("Option -%c requires an argument",
799 			    optopt));
800 		case '?':
801 			return (pcidb_usage("unknown option: -%c", optopt));
802 		}
803 	}
804 
805 	if (tablecnt > 1) {
806 		errx(EXIT_USAGE, "more than one table specified, only one of "
807 		    "-v, -d, -s, -c, -S, and -i may be specified");
808 	}
809 
810 	if (parse && fields == NULL) {
811 		errx(EXIT_USAGE, "-p requires fields specified with -o");
812 	}
813 
814 	argc -= optind;
815 	argv += optind;
816 
817 	pcidb_process_filters(argc, argv, &walk);
818 
819 	switch (table) {
820 	case PCIDB_TABLE_VENDOR:
821 		ofmt_fields = pcidb_vendor_ofmt;
822 		ofmt_fields_str = pcidb_vendor_fields;
823 		break;
824 	case PCIDB_TABLE_NONE:
825 	case PCIDB_TABLE_DEVICE:
826 		ofmt_fields = pcidb_device_ofmt;
827 		ofmt_fields_str = pcidb_device_fields;
828 		break;
829 	case PCIDB_TABLE_SUBSYSTEM:
830 		ofmt_fields = pcidb_subsystem_ofmt;
831 		ofmt_fields_str = pcidb_subsystem_fields;
832 		break;
833 	case PCIDB_TABLE_CLASS:
834 		ofmt_fields = pcidb_class_ofmt;
835 		ofmt_fields_str = pcidb_class_fields;
836 		break;
837 	case PCIDB_TABLE_SUBCLASS:
838 		ofmt_fields = pcidb_subclass_ofmt;
839 		ofmt_fields_str = pcidb_subclass_fields;
840 		break;
841 	case PCIDB_TABLE_PROGIF:
842 		ofmt_fields = pcidb_progif_ofmt;
843 		ofmt_fields_str = pcidb_progif_fields;
844 		break;
845 	}
846 
847 	if (fields == NULL) {
848 		fields = ofmt_fields_str;
849 	}
850 
851 	oferr = ofmt_open(fields, ofmt_fields, flags, 0, &ofmt);
852 	ofmt_check(oferr, parse, ofmt, pcidb_ofmt_errx, warnx);
853 
854 	hdl = pcidb_open(PCIDB_VERSION);
855 	if (hdl == NULL) {
856 		err(EXIT_FAILURE, "failed to initialize PCI IDs database");
857 	}
858 
859 	walk.pw_hdl = hdl;
860 	walk.pw_ofmt = ofmt;
861 	walk.pw_strcase = strcase;
862 
863 	switch (table) {
864 	case PCIDB_TABLE_VENDOR:
865 		pcidb_walk_vendors(&walk);
866 		break;
867 	case PCIDB_TABLE_NONE:
868 	case PCIDB_TABLE_DEVICE:
869 		pcidb_walk_devices(&walk);
870 		break;
871 	case PCIDB_TABLE_SUBSYSTEM:
872 		pcidb_walk_subsystems(&walk);
873 		break;
874 	case PCIDB_TABLE_CLASS:
875 		pcidb_walk_classes(&walk);
876 		break;
877 	case PCIDB_TABLE_SUBCLASS:
878 		pcidb_walk_subclasses(&walk);
879 		break;
880 	case PCIDB_TABLE_PROGIF:
881 		pcidb_walk_progifs(&walk);
882 		break;
883 	}
884 
885 	ofmt_close(ofmt);
886 	pcidb_close(hdl);
887 	return (EXIT_SUCCESS);
888 }
889