/* * 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 (c) 2018 Joyent Inc., All rights reserved. * Copyright 2021 RackTop Systems, Inc. * Copyright 2021 Tintri by DDN, Inc. All rights reserved. * Copyright 2023 Oxide Computer Company * Copyright 2024 Sebastian Wiedenroth */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct di_opts { boolean_t di_scripted; boolean_t di_parseable; boolean_t di_physical; boolean_t di_condensed; } di_opts_t; typedef struct di_phys { const char *dp_dev; const char *dp_serial; const char *dp_slotname; int dp_chassis; int dp_slot; int dp_faulty; int dp_locate; } di_phys_t; static void usage(const char *execname) { (void) fprintf(stderr, "Usage: %s [-Hp] [{-c|-P}]\n", execname); } static void nvlist_query_string(nvlist_t *nvl, const char *label, char **val) { if (nvlist_lookup_string(nvl, label, val) != 0) *val = "-"; } static const char * display_string(const char *label) { return ((label) ? label : "-"); } static const char * display_tristate(int val) { if (val == 0) return ("no"); if (val == 1) return ("yes"); return ("-"); } static char condensed_tristate(int val, char c) { if (val == 0) return ('-'); if (val == 1) return (c); return ('?'); } static int disk_walker(topo_hdl_t *hp, tnode_t *np, void *arg) { di_phys_t *pp = arg; topo_faclist_t fl; topo_faclist_t *lp; int e; topo_led_state_t mode; topo_led_type_t type; char *name, *slotname, *serial; boolean_t consider_label = B_TRUE; if (strcmp(topo_node_name(np), DISK) != 0) return (TOPO_WALK_NEXT); if (topo_prop_get_string(np, TOPO_PGROUP_STORAGE, TOPO_STORAGE_LOGICAL_DISK_NAME, &name, &e) != 0) { return (TOPO_WALK_NEXT); } if (strcmp(name, pp->dp_dev) != 0) return (TOPO_WALK_NEXT); if (topo_prop_get_string(np, TOPO_PGROUP_STORAGE, TOPO_STORAGE_SERIAL_NUM, &serial, &e) == 0) { pp->dp_serial = serial; } /* * There are several hierarchies of nodes that we may be dealing with. * Here are a few examples: * * chassis -> bay -> disk * chassis -> bay -> nvme -> disk * motherboard -> pcie device -> nvme -> disk * motherboard -> slot -> nvme -> disk * chassis -> port -> usb device -> disk * motherboard -> pcie device -> aic -> usb device -> disk * * The list of possibilties can go on. We want to try and see if we can * identify what tree this is so we can figure out what to do. To * accomplish this we basically walk our parent nodes looking for * information until we find everything that we expect. */ for (tnode_t *pnp = topo_node_parent(np); pnp != NULL; pnp = topo_node_parent(pnp)) { const char *pname = topo_node_name(pnp); /* * First see if this is the name of something where we can * derive the location information from and set it. We will only * consider such information from the very first bay, slot, or * usb-device that we encounter. If it is missing a label, a * label higher up in the tree will not be appropriate. */ if ((strcmp(pname, BAY) == 0 || strcmp(pname, SLOT) == 0 || strcmp(pname, USB_DEVICE) == 0) && consider_label) { consider_label = B_FALSE; if (topo_prop_get_string(pnp, TOPO_PGROUP_PROTOCOL, TOPO_PROP_LABEL, &slotname, &e) == 0) { pp->dp_slotname = slotname; } } /* * Next, see if these are nodes where we normally have * facilities. */ if (strcmp(pname, BAY) == 0) { if (topo_node_facility(hp, pnp, TOPO_FAC_TYPE_INDICATOR, TOPO_FAC_TYPE_ANY, &fl, &e) == 0) { for (lp = topo_list_next(&fl.tf_list); lp != NULL; lp = topo_list_next(lp)) { uint32_t prop; if (topo_prop_get_uint32(lp->tf_node, TOPO_PGROUP_FACILITY, TOPO_FACILITY_TYPE, &prop, &e) != 0) { continue; } type = (topo_led_type_t)prop; if (topo_prop_get_uint32(lp->tf_node, TOPO_PGROUP_FACILITY, TOPO_LED_MODE, &prop, &e) != 0) { continue; } mode = (topo_led_state_t)prop; switch (type) { case TOPO_LED_TYPE_SERVICE: pp->dp_faulty = mode ? 1 : 0; break; case TOPO_LED_TYPE_LOCATE: pp->dp_locate = mode ? 1 : 0; break; default: break; } } } } /* * Finally if this is the chassis node, we want to record its * instance number. */ if (strcmp(pname, CHASSIS) == 0) { pp->dp_chassis = topo_node_instance(pnp); } } return (TOPO_WALK_TERMINATE); } static void populate_physical(topo_hdl_t *hp, di_phys_t *pp) { int e; topo_walk_t *wp; pp->dp_faulty = pp->dp_locate = -1; pp->dp_chassis = pp->dp_slot = -1; e = 0; wp = topo_walk_init(hp, FM_FMRI_SCHEME_HC, disk_walker, pp, &e); if (wp == NULL) { errx(-1, "unable to initialise topo walker: %s", topo_strerror(e)); } while ((e = topo_walk_step(wp, TOPO_WALK_CHILD)) == TOPO_WALK_NEXT) ; if (e == TOPO_WALK_ERR) errx(-1, "topo walk failed"); topo_walk_fini(wp); } static void enumerate_disks(di_opts_t *opts) { topo_hdl_t *hp = NULL; int filter[] = { DM_DT_FIXED, -1 }; dm_descriptor_t *media; uint_t i; int e; e = 0; if ((media = dm_get_descriptors(DM_MEDIA, filter, &e)) == NULL) { errno = e; err(-1, "failed to obtain media descriptors"); } /* * We only need to walk topo if we're intending to display * condensed or physical information. If we don't need it, we leave * hp = NULL. */ if (opts->di_condensed || opts->di_physical) { e = 0; hp = topo_open(TOPO_VERSION, NULL, &e); if (hp == NULL) { errx(-1, "unable to obtain topo handle: %s", topo_strerror(e)); } e = 0; (void) topo_snap_hold(hp, NULL, &e); if (e != 0) { errx(-1, "unable to hold topo snapshot: %s", topo_strerror(e)); } } for (i = 0; media != NULL && media[i] != 0; i++) { dm_descriptor_t *disk, *controller; nvlist_t *mattrs, *dattrs; char *vid, *pid, *serial, *opath, *ctype, *pctype, *c; boolean_t removable, ssd; char device[MAXPATHLEN]; di_phys_t phys; size_t len; uint64_t size, total; uint32_t blocksize; double total_in_GiB; char sizestr[32]; char slotname[32]; char statestr[8]; if ((disk = dm_get_associated_descriptors(media[i], DM_DRIVE, &e)) == NULL) { continue; } /* * The attributes depend on us being able to get the media * info with DKIOCGMEDIAINFO which may not be the case for * disks which are failing. */ if ((mattrs = dm_get_attributes(media[i], &e)) == NULL) continue; e = nvlist_lookup_uint64(mattrs, DM_SIZE, &size); assert(e == 0); e = nvlist_lookup_uint32(mattrs, DM_BLOCKSIZE, &blocksize); assert(e == 0); vid = pid = opath = "-"; removable = B_FALSE; ssd = B_FALSE; dattrs = dm_get_attributes(disk[0], &e); if (dattrs != NULL) { nvlist_query_string(dattrs, DM_VENDOR_ID, &vid); nvlist_query_string(dattrs, DM_PRODUCT_ID, &pid); nvlist_query_string(dattrs, DM_OPATH, &opath); nvlist_query_string(dattrs, DM_SERIAL, &serial); if (nvlist_lookup_boolean(dattrs, DM_REMOVABLE) == 0) removable = B_TRUE; if (nvlist_lookup_boolean(dattrs, DM_SOLIDSTATE) == 0) ssd = B_TRUE; } pctype = "-"; ctype = NULL; if ((controller = dm_get_associated_descriptors(disk[0], DM_CONTROLLER, &e)) != NULL) { nvlist_t *cattrs; cattrs = dm_get_attributes(controller[0], &e); if (cattrs != NULL) { nvlist_query_string(cattrs, DM_CTYPE, &ctype); ctype = strdup(ctype); nvlist_free(cattrs); if (ctype != NULL) { for (c = ctype; *c != '\0'; c++) *c = toupper(*c); pctype = ctype; } } dm_free_descriptors(controller); } /* * Parse full device path to only show the device name, * i.e. c0t1d0. Many paths will reference a particular * slice (c0t1d0s0), so remove the slice if present. */ if ((c = strrchr(opath, '/')) != NULL) (void) strlcpy(device, c + 1, sizeof (device)); else (void) strlcpy(device, opath, sizeof (device)); len = strlen(device); if (device[len - 2] == 's' && (device[len - 1] >= '0' && device[len - 1] <= '9')) { device[len - 2] = '\0'; } if (hp != NULL) { bzero(&phys, sizeof (phys)); phys.dp_dev = device; populate_physical(hp, &phys); if (opts->di_parseable) { (void) snprintf(slotname, sizeof (slotname), "%d,%d", phys.dp_chassis, phys.dp_slot); } else if (phys.dp_slotname != NULL && phys.dp_chassis != -1) { (void) snprintf(slotname, sizeof (slotname), "[%d] %s", phys.dp_chassis, phys.dp_slotname); } else if (phys.dp_slotname != NULL) { (void) snprintf(slotname, sizeof (slotname), "%s", phys.dp_slotname); } else { slotname[0] = '-'; slotname[1] = '\0'; } } if (phys.dp_serial == NULL) { phys.dp_serial = serial; } /* * The size is given in blocks, so multiply the number * of blocks by the block size to get the total size, * then convert to GiB. */ total = size * blocksize; if (opts->di_parseable) { (void) snprintf(sizestr, sizeof (sizestr), "%llu", total); } else { total_in_GiB = (double)total / 1024.0 / 1024.0 / 1024.0; (void) snprintf(sizestr, sizeof (sizestr), "%7.2f GiB", total_in_GiB); } if (opts->di_condensed) { (void) snprintf(statestr, sizeof (statestr), "%c%c%c%c", condensed_tristate(phys.dp_faulty, 'F'), condensed_tristate(phys.dp_locate, 'L'), condensed_tristate(removable, 'R'), condensed_tristate(ssd, 'S')); } if (opts->di_physical) { if (opts->di_scripted) { printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n", device, vid, pid, display_string(phys.dp_serial), display_tristate(phys.dp_faulty), display_tristate(phys.dp_locate), slotname); } else { printf("%-22s %-8s %-16s " "%-20s %-3s %-3s %s\n", device, vid, pid, display_string(phys.dp_serial), display_tristate(phys.dp_faulty), display_tristate(phys.dp_locate), slotname); } } else if (opts->di_condensed) { if (opts->di_scripted) { printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", pctype, device, vid, pid, display_string(phys.dp_serial), sizestr, statestr, slotname); } else { printf("%-7s %-22s %-8s %-16s " "%-20s\n\t%-13s %-4s %s\n", pctype, device, vid, pid, display_string(phys.dp_serial), sizestr, statestr, slotname); } } else { if (opts->di_scripted) { printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n", pctype, device, vid, pid, sizestr, display_tristate(removable), display_tristate(ssd)); } else { printf("%-7s %-22s %-8s %-16s " "%-13s %-3s %-3s\n", pctype, device, vid, pid, sizestr, display_tristate(removable), display_tristate(ssd)); } } free(ctype); nvlist_free(dattrs); nvlist_free(mattrs); dm_free_descriptors(disk); } dm_free_descriptors(media); if (hp != NULL) { topo_snap_release(hp); topo_close(hp); } } int main(int argc, char *argv[]) { int c; di_opts_t opts = { .di_condensed = B_FALSE, .di_scripted = B_FALSE, .di_physical = B_FALSE, .di_parseable = B_FALSE }; while ((c = getopt(argc, argv, ":cHPp")) != EOF) { switch (c) { case 'c': if (opts.di_physical) { usage(argv[0]); errx(1, "-c and -P are mutually exclusive"); } opts.di_condensed = B_TRUE; break; case 'H': opts.di_scripted = B_TRUE; break; case 'P': if (opts.di_condensed) { usage(argv[0]); errx(1, "-c and -P are mutually exclusive"); } opts.di_physical = B_TRUE; break; case 'p': opts.di_parseable = B_TRUE; break; case '?': usage(argv[0]); errx(1, "unknown option -%c", optopt); default: errx(-1, "unexpected error on option -%c", optopt); } } if (!opts.di_scripted) { if (opts.di_physical) { printf("DISK VID PID" " SERIAL FLT LOC" " LOCATION\n"); } else if (opts.di_condensed) { printf("TYPE DISK VID PID" " SERIAL\n"); printf("\tSIZE FLRS LOCATION\n"); } else { printf("TYPE DISK VID PID" " SIZE RMV SSD\n"); } } enumerate_disks(&opts); return (0); }