/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2024 Oxide Computer Company */ /* * A tool to interface with the pci.ids database driven by libpcidb. */ #include #include #include #include #include #include #include #include #include #include #include #include #define EXIT_USAGE 2 static char *pcidb_progname; typedef enum { PCIDB_MODE_UNKNOWN, PCIDB_MODE_LIST, PCIDB_MODE_SEARCH, PCIDB_MODE_LOOKUP } pcidb_mode_t; typedef enum { PCIDB_TABLE_NONE, PCIDB_TABLE_VENDOR, PCIDB_TABLE_DEVICE, PCIDB_TABLE_SUBSYSTEM, PCIDB_TABLE_CLASS, PCIDB_TABLE_SUBCLASS, PCIDB_TABLE_PROGIF } pcidb_table_t; typedef enum { PCIDB_TGRP_NONE, PCIDB_TGRP_DEV, PCIDB_TGRP_CLASS } pcidb_tgrp_t; typedef enum { PCIDB_OFMT_VID, PCIDB_OFMT_VENSTR, PCIDB_OFMT_DID, PCIDB_OFMT_DEVSTR, PCIDB_OFMT_SVID, PCIDB_OFMT_SDID, PCIDB_OFMT_SUBVENSTR, PCIDB_OFMT_SUBSYSSTR, PCIDB_OFMT_BCC, PCIDB_OFMT_CLASSSTR, PCIDB_OFMT_SCC, PCIDB_OFMT_SUBCLASSSTR, PCIDB_OFMT_PI, PCIDB_OFMT_PROGIFSTR } pcidb_ofmt_t; typedef struct pcidb_filter { const char *pft_raw; boolean_t pft_used; pcidb_table_t pft_table; pcidb_tgrp_t pft_tgrp; uint32_t pft_vend; uint32_t pft_dev; uint32_t pft_subven; uint32_t pft_subdev; uint32_t pft_class; uint32_t pft_subclass; uint32_t pft_progif; } pcidb_filter_t; #define PCIDB_NOFILTER UINT32_MAX typedef struct pcidb_walk { pcidb_hdl_t *pw_hdl; ofmt_handle_t pw_ofmt; pcidb_vendor_t *pw_vendor; pcidb_device_t *pw_device; pcidb_subvd_t *pw_subvd; pcidb_class_t *pw_class; pcidb_subclass_t *pw_subclass; pcidb_progif_t *pw_progif; boolean_t pw_strcase; uint_t pw_nfilters; pcidb_filter_t *pw_filters; } pcidb_walk_t; static boolean_t pcidb_write_vendor(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { pcidb_walk_t *walk = ofarg->ofmt_cbarg; VERIFY(walk->pw_vendor != NULL); switch (ofarg->ofmt_id) { case PCIDB_OFMT_VID: (void) snprintf(buf, buflen, "%x", pcidb_vendor_id(walk->pw_vendor)); break; case PCIDB_OFMT_VENSTR: (void) strlcpy(buf, pcidb_vendor_name(walk->pw_vendor), buflen); break; default: abort(); } return (B_TRUE); } static boolean_t pcidb_write_device(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { pcidb_walk_t *walk = ofarg->ofmt_cbarg; VERIFY(walk->pw_device != NULL); switch (ofarg->ofmt_id) { case PCIDB_OFMT_DID: (void) snprintf(buf, buflen, "%x", pcidb_device_id(walk->pw_device)); break; case PCIDB_OFMT_DEVSTR: (void) strlcpy(buf, pcidb_device_name(walk->pw_device), buflen); break; default: abort(); } return (B_TRUE); } static boolean_t pcidb_write_subsystem(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { pcidb_walk_t *walk = ofarg->ofmt_cbarg; pcidb_vendor_t *vendor; VERIFY(walk->pw_subvd != NULL); switch (ofarg->ofmt_id) { case PCIDB_OFMT_SVID: (void) snprintf(buf, buflen, "%x", pcidb_subvd_svid(walk->pw_subvd)); break; case PCIDB_OFMT_SDID: (void) snprintf(buf, buflen, "%x", pcidb_subvd_sdid(walk->pw_subvd)); break; case PCIDB_OFMT_SUBSYSSTR: (void) strlcpy(buf, pcidb_subvd_name(walk->pw_subvd), buflen); break; case PCIDB_OFMT_SUBVENSTR: vendor = pcidb_lookup_vendor(walk->pw_hdl, pcidb_subvd_svid(walk->pw_subvd)); if (vendor == NULL) { return (B_FALSE); } (void) strlcpy(buf, pcidb_vendor_name(vendor), buflen); break; default: abort(); } return (B_TRUE); } static boolean_t pcidb_write_class(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { pcidb_walk_t *walk = ofarg->ofmt_cbarg; VERIFY(walk->pw_class != NULL); switch (ofarg->ofmt_id) { case PCIDB_OFMT_BCC: (void) snprintf(buf, buflen, "%x", pcidb_class_code(walk->pw_class)); break; case PCIDB_OFMT_CLASSSTR: (void) strlcpy(buf, pcidb_class_name(walk->pw_class), buflen); break; default: abort(); } return (B_TRUE); } static boolean_t pcidb_write_subclass(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { pcidb_walk_t *walk = ofarg->ofmt_cbarg; VERIFY(walk->pw_subclass != NULL); switch (ofarg->ofmt_id) { case PCIDB_OFMT_SCC: (void) snprintf(buf, buflen, "%x", pcidb_subclass_code(walk->pw_subclass)); break; case PCIDB_OFMT_SUBCLASSSTR: (void) strlcpy(buf, pcidb_subclass_name(walk->pw_subclass), buflen); break; default: abort(); } return (B_TRUE); } static boolean_t pcidb_write_progif(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { pcidb_walk_t *walk = ofarg->ofmt_cbarg; VERIFY(walk->pw_progif != NULL); switch (ofarg->ofmt_id) { case PCIDB_OFMT_PI: (void) snprintf(buf, buflen, "%x", pcidb_progif_code(walk->pw_progif)); break; case PCIDB_OFMT_PROGIFSTR: (void) strlcpy(buf, pcidb_progif_name(walk->pw_progif), buflen); break; default: abort(); } return (B_TRUE); } static const char *pcidb_vendor_fields = "vid,vendor"; static const ofmt_field_t pcidb_vendor_ofmt[] = { { "VID", 8, PCIDB_OFMT_VID, pcidb_write_vendor }, { "VENDOR", 30, PCIDB_OFMT_VENSTR, pcidb_write_vendor }, { NULL, 0, 0, NULL } }; static const char *pcidb_device_fields = "vid,did,vendor,device"; static const ofmt_field_t pcidb_device_ofmt[] = { { "VID", 8, PCIDB_OFMT_VID, pcidb_write_vendor }, { "VENDOR", 30, PCIDB_OFMT_VENSTR, pcidb_write_vendor }, { "DID", 8, PCIDB_OFMT_DID, pcidb_write_device }, { "DEVICE", 30, PCIDB_OFMT_DEVSTR, pcidb_write_device }, { NULL, 0, 0, NULL } }; static const char *pcidb_subsystem_fields = "vid,did,svid,sdid,subsystem"; static const ofmt_field_t pcidb_subsystem_ofmt[] = { { "VID", 8, PCIDB_OFMT_VID, pcidb_write_vendor }, { "VENDOR", 30, PCIDB_OFMT_VENSTR, pcidb_write_vendor }, { "DID", 8, PCIDB_OFMT_DID, pcidb_write_device }, { "DEVICE", 30, PCIDB_OFMT_DEVSTR, pcidb_write_device }, { "SVID", 8, PCIDB_OFMT_SVID, pcidb_write_subsystem }, { "SDID", 8, PCIDB_OFMT_SDID, pcidb_write_subsystem }, { "SUBSYSTEM", 30, PCIDB_OFMT_SUBSYSSTR, pcidb_write_subsystem }, { "SUBVENDOR", 30, PCIDB_OFMT_SUBVENSTR, pcidb_write_subsystem }, { NULL, 0, 0, NULL } }; static const char *pcidb_class_fields = "bcc,class"; static const ofmt_field_t pcidb_class_ofmt[] = { { "BCC", 6, PCIDB_OFMT_BCC, pcidb_write_class }, { "CLASS", 30, PCIDB_OFMT_CLASSSTR, pcidb_write_class }, { NULL, 0, 0, NULL } }; static const char *pcidb_subclass_fields = "bcc,scc,class,subclass"; static const ofmt_field_t pcidb_subclass_ofmt[] = { { "BCC", 6, PCIDB_OFMT_BCC, pcidb_write_class }, { "CLASS", 30, PCIDB_OFMT_CLASSSTR, pcidb_write_class }, { "SCC", 6, PCIDB_OFMT_SCC, pcidb_write_subclass }, { "SUBCLASS", 30, PCIDB_OFMT_SUBCLASSSTR, pcidb_write_subclass }, { NULL, 0, 0, NULL } }; static const char *pcidb_progif_fields = "bcc,scc,pi,subclass,interface"; static const ofmt_field_t pcidb_progif_ofmt[] = { { "BCC", 6, PCIDB_OFMT_BCC, pcidb_write_class }, { "CLASS", 30, PCIDB_OFMT_CLASSSTR, pcidb_write_class }, { "SCC", 6, PCIDB_OFMT_SCC, pcidb_write_subclass }, { "SUBCLASS", 30, PCIDB_OFMT_SUBCLASSSTR, pcidb_write_subclass }, { "PI", 6, PCIDB_OFMT_PI, pcidb_write_progif }, { "INTERFACE", 30, PCIDB_OFMT_PROGIFSTR, pcidb_write_progif }, { NULL, 0, 0, NULL } }; static void pcidb_ofmt_errx(const char *fmt, ...) { va_list ap; va_start(ap, fmt); verrx(EXIT_FAILURE, fmt, ap); } /* * Check to see if any of our filters match. Note, our filters may overlap and * one may be more specific than another. As a result, once we find a match we * check all remaining filters and check if any of them would also match this * just to reduce the chance of user error. For example, if we didn't do this a * series of filters such as "pci8086 pci8086,10d3" will cause the latter to * never be matched. */ static boolean_t pcidb_filter_match(pcidb_walk_t *walk) { boolean_t match = B_FALSE; pcidb_tgrp_t grp = PCIDB_TGRP_NONE; if (walk->pw_nfilters == 0) { return (B_TRUE); } for (uint_t i = 0; i < walk->pw_nfilters; i++) { pcidb_filter_t *filt = &walk->pw_filters[i]; if (match && (filt->pft_used || grp != filt->pft_tgrp)) { continue; } if (filt->pft_vend != PCIDB_NOFILTER && (walk->pw_vendor == NULL || filt->pft_vend != pcidb_vendor_id(walk->pw_vendor))) { continue; } if (filt->pft_dev != PCIDB_NOFILTER && (walk->pw_device == NULL || filt->pft_dev != pcidb_device_id(walk->pw_device))) { continue; } if (filt->pft_subven != PCIDB_NOFILTER && (walk->pw_subvd == NULL || filt->pft_subven != pcidb_subvd_svid(walk->pw_subvd))) { continue; } if (filt->pft_subdev != PCIDB_NOFILTER && (walk->pw_subvd == NULL || filt->pft_subdev != pcidb_subvd_sdid(walk->pw_subvd))) { continue; } if (filt->pft_class != PCIDB_NOFILTER && (walk->pw_class == NULL || filt->pft_class != pcidb_class_code(walk->pw_class))) { continue; } if (filt->pft_subclass != PCIDB_NOFILTER && (walk->pw_subclass == NULL || filt->pft_subclass != pcidb_subclass_code(walk->pw_subclass))) { continue; } if (filt->pft_progif != PCIDB_NOFILTER && (walk->pw_progif == NULL || filt->pft_progif != pcidb_progif_code(walk->pw_progif))) { continue; } filt->pft_used = B_TRUE; grp = filt->pft_tgrp; match = B_TRUE; } return (match); } static void pcidb_walk_vendors(pcidb_walk_t *walk) { pcidb_hdl_t *hdl = walk->pw_hdl; for (pcidb_vendor_t *vend = pcidb_vendor_iter(hdl); vend != NULL; vend = pcidb_vendor_iter_next(vend)) { walk->pw_vendor = vend; if (!pcidb_filter_match(walk)) continue; ofmt_print(walk->pw_ofmt, walk); } } static void pcidb_walk_devices(pcidb_walk_t *walk) { pcidb_hdl_t *hdl = walk->pw_hdl; for (pcidb_vendor_t *vend = pcidb_vendor_iter(hdl); vend != NULL; vend = pcidb_vendor_iter_next(vend)) { walk->pw_vendor = vend; for (pcidb_device_t *dev = pcidb_device_iter(vend); dev != NULL; dev = pcidb_device_iter_next(dev)) { walk->pw_device = dev; if (!pcidb_filter_match(walk)) continue; ofmt_print(walk->pw_ofmt, walk); } } } static void pcidb_walk_subsystems(pcidb_walk_t *walk) { pcidb_hdl_t *hdl = walk->pw_hdl; for (pcidb_vendor_t *vend = pcidb_vendor_iter(hdl); vend != NULL; vend = pcidb_vendor_iter_next(vend)) { walk->pw_vendor = vend; for (pcidb_device_t *dev = pcidb_device_iter(vend); dev != NULL; dev = pcidb_device_iter_next(dev)) { walk->pw_device = dev; for (pcidb_subvd_t *sub = pcidb_subvd_iter(dev); sub != NULL; sub = pcidb_subvd_iter_next(sub)) { walk->pw_subvd = sub; if (!pcidb_filter_match(walk)) continue; ofmt_print(walk->pw_ofmt, walk); } } } } static void pcidb_walk_classes(pcidb_walk_t *walk) { for (pcidb_class_t *class = pcidb_class_iter(walk->pw_hdl); class != NULL; class = pcidb_class_iter_next(class)) { walk->pw_class = class; if (!pcidb_filter_match(walk)) continue; ofmt_print(walk->pw_ofmt, walk); } } static void pcidb_walk_subclasses(pcidb_walk_t *walk) { for (pcidb_class_t *class = pcidb_class_iter(walk->pw_hdl); class != NULL; class = pcidb_class_iter_next(class)) { walk->pw_class = class; for (pcidb_subclass_t *sub = pcidb_subclass_iter(class); sub != NULL; sub = pcidb_subclass_iter_next(sub)) { walk->pw_subclass = sub; if (!pcidb_filter_match(walk)) continue; ofmt_print(walk->pw_ofmt, walk); } } } static void pcidb_walk_progifs(pcidb_walk_t *walk) { for (pcidb_class_t *class = pcidb_class_iter(walk->pw_hdl); class != NULL; class = pcidb_class_iter_next(class)) { walk->pw_class = class; for (pcidb_subclass_t *sub = pcidb_subclass_iter(class); sub != NULL; sub = pcidb_subclass_iter_next(sub)) { walk->pw_subclass = sub; for (pcidb_progif_t *progif = pcidb_progif_iter(sub); progif != NULL; progif = pcidb_progif_iter_next(progif)) { walk->pw_progif = progif; if (!pcidb_filter_match(walk)) continue; ofmt_print(walk->pw_ofmt, walk); } } } } static void pcidb_parse_class_filter(pcidb_filter_t *filter, char *arg, const char *orig) { size_t len; unsigned long val; char *eptr; filter->pft_vend = filter->pft_dev = PCIDB_NOFILTER; filter->pft_subven = filter->pft_subdev = PCIDB_NOFILTER; len = strlen(arg); if (len != 2 && len != 4 && len != 6) { errx(EXIT_FAILURE, "invalid class filter: '%s': bad length", orig); } errno = 0; val = strtoul(arg, &eptr, 16); if (errno != 0 || *eptr != '\0') { errx(EXIT_FAILURE, "invalid class filter: '%s': failed to " "parse hex string", orig); } if (len == 6) { filter->pft_progif = val & 0xff; val = val >> 8; } else { filter->pft_progif = PCIDB_NOFILTER; } if (len >= 4) { filter->pft_subclass = val & 0xff; val = val >> 8; } else { filter->pft_subclass = PCIDB_NOFILTER; } filter->pft_class = val & 0xff; } static void pcidb_parse_device_filter(pcidb_filter_t *filter, char *arg, const char *orig) { unsigned long val; uint32_t primary, secondary; char *eptr; filter->pft_vend = filter->pft_dev = PCIDB_NOFILTER; filter->pft_subven = filter->pft_subdev = PCIDB_NOFILTER; filter->pft_class = filter->pft_subclass = PCIDB_NOFILTER; filter->pft_progif = PCIDB_NOFILTER; errno = 0; val = strtoul(arg, &eptr, 16); if (errno != 0 || (*eptr != '\0' && *eptr != ',')) { errx(EXIT_FAILURE, "invalid device filter: '%s': failed to " "parse hex string", orig); } if (val > UINT16_MAX) { errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff", val); } primary = (uint32_t)val; if (*eptr == '\0') { filter->pft_vend = primary; return; } else if (strcmp(eptr, ",s") == 0) { filter->pft_subven = primary; return; } else if (eptr[1] == '\0') { errx(EXIT_FAILURE, "invalid device filter: '%s': filter " "terminated early", arg); } arg = eptr + 1; val = strtoul(arg, &eptr, 16); if (errno != 0 || (*eptr != '\0' && *eptr != ',' && *eptr != '.')) { errx(EXIT_FAILURE, "invalid device filter: '%s': failed to " "parse hex string at %s", orig, arg); } if (val > UINT16_MAX) { errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff", val); } secondary = (uint32_t)val; if (*eptr == '\0') { filter->pft_vend = primary; filter->pft_dev = secondary; return; } else if (eptr[1] == '\0') { errx(EXIT_FAILURE, "invalid device filter: '%s': filter " "terminated early", arg); } if (*eptr == ',') { if (eptr[1] == 'p' && eptr[2] == '\0') { filter->pft_vend = primary; filter->pft_dev = secondary; return; } if (eptr[1] == 's' && eptr[2] == '\0') { filter->pft_subven = primary; filter->pft_subdev = secondary; return; } errx(EXIT_FAILURE, "invalid device filter: '%s': invalid " "trailing comma at %s, expected either ,p or ,s", orig, eptr); } filter->pft_vend = primary; filter->pft_dev = secondary; arg = eptr + 1; errno = 0; val = strtoul(arg, &eptr, 16); if (errno != 0 || (*eptr != '\0' && *eptr != ',')) { errx(EXIT_FAILURE, "invalid device filter: '%s': failed to " "parse hex string at %s", orig, arg); } if (val > UINT16_MAX) { errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff", val); } filter->pft_subven = (uint32_t)val; if (*eptr == '\0') { return; } else if (eptr[1] == '\0') { errx(EXIT_FAILURE, "invalid device filter: '%s': filter " "terminated early", arg); } arg = eptr + 1; errno = 0; val = strtoul(arg, &eptr, 16); if (errno != 0 || *eptr != '\0') { errx(EXIT_FAILURE, "invalid device filter: '%s': failed to " "parse hex string at %s", orig, arg); } if (val > UINT16_MAX) { errx(EXIT_FAILURE, "invalid id: %lx is larger than 0xffff", val); } filter->pft_subdev = (uint32_t)val; } static pcidb_table_t pcidb_filter_to_table(const pcidb_filter_t *filter) { if (filter->pft_progif != PCIDB_NOFILTER) { return (PCIDB_TABLE_PROGIF); } else if (filter->pft_subclass != PCIDB_NOFILTER) { return (PCIDB_TABLE_SUBCLASS); } else if (filter->pft_class != PCIDB_NOFILTER) { return (PCIDB_TABLE_CLASS); } else if (filter->pft_subven != PCIDB_NOFILTER || filter->pft_subdev != PCIDB_NOFILTER) { return (PCIDB_TABLE_SUBSYSTEM); } else if (filter->pft_dev != PCIDB_NOFILTER) { return (PCIDB_TABLE_DEVICE); } else { VERIFY3U(filter->pft_vend, !=, PCIDB_NOFILTER); return (PCIDB_TABLE_VENDOR); } } static const char * pcidb_table_to_string(pcidb_table_t table) { switch (table) { case PCIDB_TABLE_VENDOR: return ("vendor"); case PCIDB_TABLE_DEVICE: return ("device"); case PCIDB_TABLE_SUBSYSTEM: return ("subsystem"); case PCIDB_TABLE_CLASS: return ("class"); case PCIDB_TABLE_SUBCLASS: return ("subclass"); case PCIDB_TABLE_PROGIF: return ("programming interface"); case PCIDB_TABLE_NONE: return ("none"); default: abort(); } } static pcidb_tgrp_t pcidb_table_to_group(pcidb_table_t table) { switch (table) { case PCIDB_TABLE_VENDOR: case PCIDB_TABLE_DEVICE: case PCIDB_TABLE_SUBSYSTEM: return (PCIDB_TGRP_DEV); case PCIDB_TABLE_CLASS: case PCIDB_TABLE_SUBCLASS: case PCIDB_TABLE_PROGIF: return (PCIDB_TGRP_CLASS); case PCIDB_TABLE_NONE: return (PCIDB_TGRP_NONE); default: abort(); } } /* * PCIDB_TABLE_NONE is not in here as it should only be a sentinal value and not * something that users see. */ static const char * pcidb_table_to_grpstr(pcidb_table_t table) { switch (pcidb_table_to_group(table)) { case PCIDB_TGRP_DEV: return ("vendor/device/subsystem"); case PCIDB_TGRP_CLASS: return ("class/subclass/progif"); default: abort(); } } /* * Process a series of alias style ways of indicating numeric filters. Use the * basic alias format for now. */ static void pcidb_parse_filters(int argc, char *argv[], pcidb_walk_t *walkp) { if (argc <= 0) { walkp->pw_nfilters = 0; return; } walkp->pw_nfilters = argc; walkp->pw_filters = calloc(walkp->pw_nfilters, sizeof (pcidb_filter_t)); if (walkp->pw_filters == NULL) { err(EXIT_FAILURE, "failed to allocate memory for filters"); } for (int i = 0; i < argc; i++) { char *str = strdup(argv[i]); if (str == NULL) { errx(EXIT_FAILURE, "failed to duplicate string %s", argv[i]); } if (strncmp(str, "pciexclass,", 11) == 0) { pcidb_parse_class_filter(&walkp->pw_filters[i], str + 11, argv[i]); } else if (strncmp(str, "pciclass,", 9) == 0) { pcidb_parse_class_filter(&walkp->pw_filters[i], str + 9, argv[i]); } else if (strncmp(str, "pciex", 5) == 0) { pcidb_parse_device_filter(&walkp->pw_filters[i], str + 5, argv[i]); } else if (strncmp(str, "pci", 3) == 0) { pcidb_parse_device_filter(&walkp->pw_filters[i], str + 3, argv[i]); } else { errx(EXIT_FAILURE, "invalid filter string: %s", str); } free(str); walkp->pw_filters[i].pft_raw = argv[i]; walkp->pw_filters[i].pft_used = B_FALSE; walkp->pw_filters[i].pft_table = pcidb_filter_to_table(&walkp->pw_filters[i]); walkp->pw_filters[i].pft_tgrp = pcidb_table_to_group(walkp->pw_filters[i].pft_table); } } /* * Determine if the set of filters is mutually consistent with the filters that * we have been requested. Our goal is to prevent a user from specifying * something that is basically unsatisfiable. For example, if they ask for a * filter related to devices but have specified the class table or vice versa. * Similarly, if someone has specified no table on the command line then we * should pick a default that is actually usable. As such we have a few rules * that we check: * * - All filters must be in the same group of table. That is either in the * vendor/device/subsystem group or the class/subclass/progif group. * - A less specific filter for a group is always valid for a more specific * table. * - A more specific filter for a group is always invalid for a less specific * table. * - If the user did not request the table, we can automatically move the * filter to the more specific value. */ static void pcidb_validate_filters(const pcidb_walk_t *walk, pcidb_table_t *table) { pcidb_table_t cur = *table; boolean_t tset = cur != PCIDB_TABLE_NONE; for (uint_t i = 0; i < walk->pw_nfilters; i++) { const pcidb_filter_t *filt = &walk->pw_filters[i]; /* * If we have the same table as the current one, then there is * nothing to do. */ if (cur == filt->pft_table) { continue; } /* * When there is no current table, which implies tset is false, * then we can always change this around. Note, if someone asks * for the vendor table, we want to try to use the device table * as the default like when there are no filters specified. */ if (cur == PCIDB_TABLE_NONE) { cur = filt->pft_table; if (cur == PCIDB_TABLE_VENDOR) cur = PCIDB_TABLE_DEVICE; continue; } if (pcidb_table_to_group(cur) != filt->pft_tgrp) { errx(EXIT_FAILURE, "filter %s targets the %s table, " "but other filters target the %s group of tables: " "both cannot be used at the same time", filt->pft_raw, pcidb_table_to_string(filt->pft_table), pcidb_table_to_grpstr(cur)); } /* * This confirms the current filter is less than the target. The * equality case was handled up above. */ if (filt->pft_table < cur) { continue; } /* * We require a more specific table than we currently have. If * this hasn't been requested, then it's fine. Otherwise it's an * error. We can't change an explicitly requested table out from * a user. */ if (tset) { errx(EXIT_FAILURE, "filter %s needs to match against " "table %s, which is more specific than the " "requested table %s", filt->pft_raw, pcidb_table_to_string(filt->pft_table), pcidb_table_to_string(cur)); } cur = filt->pft_table; } *table = cur; } static void pcidb_drop_privs(void) { priv_set_t *curprivs, *targprivs; if ((curprivs = priv_allocset()) == NULL) { err(EXIT_FAILURE, "failed to allocate privilege set to drop " "privs"); } if (getppriv(PRIV_EFFECTIVE, curprivs) != 0) { err(EXIT_FAILURE, "failed to get current privileges"); } if ((targprivs = priv_allocset()) == NULL) { err(EXIT_FAILURE, "failed to allocate privilege set to drop " "privs"); } /* * Set our privileges to the minimum required. Because stdout will have * already been opened, all we need is the ability to read files from * basic privileges. We opt to keep FILE_DAC_READ if the caller has it * just in case there is something weird about the location of the * pci.ids files. */ priv_basicset(targprivs); VERIFY0(priv_delset(targprivs, PRIV_FILE_LINK_ANY)); VERIFY0(priv_delset(targprivs, PRIV_PROC_INFO)); VERIFY0(priv_delset(targprivs, PRIV_PROC_SESSION)); VERIFY0(priv_delset(targprivs, PRIV_PROC_FORK)); VERIFY0(priv_delset(targprivs, PRIV_NET_ACCESS)); VERIFY0(priv_delset(targprivs, PRIV_FILE_WRITE)); VERIFY0(priv_delset(targprivs, PRIV_PROC_EXEC)); VERIFY0(priv_addset(targprivs, PRIV_FILE_DAC_READ)); priv_intersect(curprivs, targprivs); if (setppriv(PRIV_SET, PRIV_EFFECTIVE, targprivs) != 0) { err(EXIT_FAILURE, "failed to reduce privileges"); } priv_freeset(curprivs); priv_freeset(targprivs); } static int pcidb_usage(const char *fmt, ...) { if (fmt != NULL) { va_list ap; (void) fprintf(stderr, "%s: ", pcidb_progname); va_start(ap, fmt); (void) vfprintf(stderr, fmt, ap); va_end(ap); (void) fprintf(stderr, "\n"); } (void) fprintf(stderr, "usage: %s [-v|-d|-s|-c|-S|-i] [-H]" "[[-p] [-o [,...]] []\n\n" "\t-v\t\tshow vendor table\n" "\t-d\t\tshow device table\n" "\t-s\t\tshow subsystem table\n" "\t-c\t\tshow class table\n" "\t-S\t\tshow subclass table\n" "\t-i\t\tshow programming interface table\n" "\t-H\t\tdo not output column headers\n" "\t-p\t\toutput in parsable form\n" "\t-o field\toutput only specified fields\n\n" "filters take the form of PCI aliases, e.g. pci8086,1522, " "pci1028,1f44,s, or\n" "pciex1022,1480.1462,7c37. Classes can be specified in a similar " "way, e.g.\npciclass,010802 or pciclass,0403.\n\n" "If no table is specified, then a table will be picked based on " "the specified\nfilters. The default is to use the device table.\n", pcidb_progname); return (EXIT_USAGE); } int main(int argc, char *argv[]) { pcidb_hdl_t *hdl; int c, ret; uint_t tablecnt = 0; pcidb_table_t table = PCIDB_TABLE_NONE; boolean_t parse = B_FALSE, strcase = B_FALSE; const char *fields = NULL; const char *ofmt_fields_str = NULL; const ofmt_field_t *ofmt_fields = NULL; ofmt_handle_t ofmt; ofmt_status_t oferr; uint_t flags = 0; pcidb_walk_t walk; bzero(&walk, sizeof (walk)); pcidb_progname = basename(argv[0]); pcidb_drop_privs(); while ((c = getopt(argc, argv, ":vdscSipo:hH")) != -1) { switch (c) { case 'v': tablecnt++; table = PCIDB_TABLE_VENDOR; break; case 'd': tablecnt++; table = PCIDB_TABLE_DEVICE; break; case 's': tablecnt++; table = PCIDB_TABLE_SUBSYSTEM; break; case 'c': tablecnt++; table = PCIDB_TABLE_CLASS; break; case 'S': tablecnt++; table = PCIDB_TABLE_SUBCLASS; break; case 'i': tablecnt++; table = PCIDB_TABLE_PROGIF; break; case 'p': parse = B_TRUE; flags |= OFMT_PARSABLE; break; case 'o': fields = optarg; break; case 'h': return (pcidb_usage(NULL)); case 'H': flags |= OFMT_NOHEADER; break; case ':': return (pcidb_usage("Option -%c requires an argument", optopt)); case '?': return (pcidb_usage("unknown option: -%c", optopt)); } } if (tablecnt > 1) { errx(EXIT_USAGE, "more than one table specified, only one of " "-v, -d, -s, -c, -S, and -i may be specified"); } if (parse && fields == NULL) { errx(EXIT_USAGE, "-p requires fields specified with -o"); } argc -= optind; argv += optind; /* * Determine that the set of filters we have been asked for makes sense * with the table that's been requested. If no table has been requested, * then go ahead and adjust our table to this. If there's still no table * that's been asked for because there are no filters, then we just go * to the default of the device table. */ pcidb_parse_filters(argc, argv, &walk); pcidb_validate_filters(&walk, &table); switch (table) { case PCIDB_TABLE_VENDOR: ofmt_fields = pcidb_vendor_ofmt; ofmt_fields_str = pcidb_vendor_fields; break; case PCIDB_TABLE_NONE: case PCIDB_TABLE_DEVICE: ofmt_fields = pcidb_device_ofmt; ofmt_fields_str = pcidb_device_fields; break; case PCIDB_TABLE_SUBSYSTEM: ofmt_fields = pcidb_subsystem_ofmt; ofmt_fields_str = pcidb_subsystem_fields; break; case PCIDB_TABLE_CLASS: ofmt_fields = pcidb_class_ofmt; ofmt_fields_str = pcidb_class_fields; break; case PCIDB_TABLE_SUBCLASS: ofmt_fields = pcidb_subclass_ofmt; ofmt_fields_str = pcidb_subclass_fields; break; case PCIDB_TABLE_PROGIF: ofmt_fields = pcidb_progif_ofmt; ofmt_fields_str = pcidb_progif_fields; break; } if (fields == NULL) { fields = ofmt_fields_str; } oferr = ofmt_open(fields, ofmt_fields, flags, 0, &ofmt); ofmt_check(oferr, parse, ofmt, pcidb_ofmt_errx, warnx); hdl = pcidb_open(PCIDB_VERSION); if (hdl == NULL) { err(EXIT_FAILURE, "failed to initialize PCI IDs database"); } walk.pw_hdl = hdl; walk.pw_ofmt = ofmt; walk.pw_strcase = strcase; switch (table) { case PCIDB_TABLE_VENDOR: pcidb_walk_vendors(&walk); break; case PCIDB_TABLE_NONE: case PCIDB_TABLE_DEVICE: pcidb_walk_devices(&walk); break; case PCIDB_TABLE_SUBSYSTEM: pcidb_walk_subsystems(&walk); break; case PCIDB_TABLE_CLASS: pcidb_walk_classes(&walk); break; case PCIDB_TABLE_SUBCLASS: pcidb_walk_subclasses(&walk); break; case PCIDB_TABLE_PROGIF: pcidb_walk_progifs(&walk); break; } ofmt_close(ofmt); pcidb_close(hdl); /* * Check that all filters were used. We don't bother with checking if we * printed anything more broadly because we know that the database will * always have something in there so no filters should always print * something. */ ret = EXIT_SUCCESS; for (uint_t i = 0; i < walk.pw_nfilters; i++) { if (!walk.pw_filters[i].pft_used) { warnx("filter '%s' did not match anything", walk.pw_filters[i].pft_raw); ret = EXIT_FAILURE; } } return (ret); }