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 (c) 2018 Joyent Inc., All rights reserved. 14 * Copyright 2021 RackTop Systems, Inc. 15 * Copyright 2021 Tintri by DDN, Inc. All rights reserved. 16 * Copyright 2023 Oxide Computer Company 17 * Copyright 2024 Sebastian Wiedenroth 18 * Copyright 2026 Jason King 19 */ 20 21 #include <sys/types.h> 22 #include <sys/stat.h> 23 #include <fcntl.h> 24 #include <errno.h> 25 #include <string.h> 26 #include <stdio.h> 27 #include <unistd.h> 28 #include <limits.h> 29 #include <assert.h> 30 #include <ctype.h> 31 #include <stdarg.h> 32 #include <strings.h> 33 #include <err.h> 34 35 #include <libdiskmgt.h> 36 #include <sys/nvpair.h> 37 #include <sys/param.h> 38 #include <sys/ccompile.h> 39 40 #include <fm/libtopo.h> 41 #include <fm/topo_hc.h> 42 #include <fm/topo_list.h> 43 #include <sys/fm/protocol.h> 44 #include <modules/common/disk/disk.h> 45 46 typedef struct di_opts { 47 boolean_t di_scripted; 48 boolean_t di_parseable; 49 boolean_t di_physical; 50 boolean_t di_condensed; 51 } di_opts_t; 52 53 typedef struct di_phys { 54 const char *dp_dev; 55 const char *dp_serial; 56 const char *dp_slotname; 57 int dp_chassis; 58 int dp_slot; 59 int dp_faulty; 60 int dp_locate; 61 } di_phys_t; 62 63 static void 64 usage(const char *execname) 65 { 66 (void) fprintf(stderr, "Usage: %s [-Hp] [{-c|-P}]\n", execname); 67 } 68 69 static void 70 nvlist_query_string(nvlist_t *nvl, const char *label, char **val) 71 { 72 if (nvlist_lookup_string(nvl, label, val) != 0) 73 *val = "-"; 74 } 75 76 static const char * 77 display_string(const char *label) 78 { 79 return ((label) ? label : "-"); 80 } 81 82 static const char * 83 display_tristate(int val) 84 { 85 if (val == 0) 86 return ("no"); 87 if (val == 1) 88 return ("yes"); 89 90 return ("-"); 91 } 92 93 static char 94 condensed_tristate(int val, char c) 95 { 96 if (val == 0) 97 return ('-'); 98 if (val == 1) 99 return (c); 100 101 return ('?'); 102 } 103 static int 104 disk_walker(topo_hdl_t *hp, tnode_t *np, void *arg) 105 { 106 di_phys_t *pp = arg; 107 topo_faclist_t fl; 108 topo_faclist_t *lp; 109 int e; 110 topo_led_state_t mode; 111 topo_led_type_t type; 112 char *name, *slotname, *serial; 113 boolean_t consider_label = B_TRUE; 114 115 if (strcmp(topo_node_name(np), DISK) != 0) 116 return (TOPO_WALK_NEXT); 117 118 if (topo_prop_get_string(np, TOPO_PGROUP_STORAGE, 119 TOPO_STORAGE_LOGICAL_DISK_NAME, &name, &e) != 0) { 120 return (TOPO_WALK_NEXT); 121 } 122 123 if (strcmp(name, pp->dp_dev) != 0) 124 return (TOPO_WALK_NEXT); 125 126 if (topo_prop_get_string(np, TOPO_PGROUP_STORAGE, 127 TOPO_STORAGE_SERIAL_NUM, &serial, &e) == 0) { 128 pp->dp_serial = serial; 129 } 130 131 /* 132 * There are several hierarchies of nodes that we may be dealing with. 133 * Here are a few examples: 134 * 135 * chassis -> bay -> disk 136 * chassis -> bay -> nvme -> disk 137 * motherboard -> pcie device -> nvme -> disk 138 * motherboard -> slot -> nvme -> disk 139 * chassis -> port -> usb device -> disk 140 * motherboard -> pcie device -> aic -> usb device -> disk 141 * 142 * The list of possibilties can go on. We want to try and see if we can 143 * identify what tree this is so we can figure out what to do. To 144 * accomplish this we basically walk our parent nodes looking for 145 * information until we find everything that we expect. 146 */ 147 for (tnode_t *pnp = topo_node_parent(np); pnp != NULL; 148 pnp = topo_node_parent(pnp)) { 149 const char *pname = topo_node_name(pnp); 150 151 /* 152 * First see if this is the name of something where we can 153 * derive the location information from and set it. We will only 154 * consider such information from the very first bay, slot, or 155 * usb-device that we encounter. If it is missing a label, a 156 * label higher up in the tree will not be appropriate. 157 */ 158 if ((strcmp(pname, BAY) == 0 || strcmp(pname, SLOT) == 0 || 159 strcmp(pname, USB_DEVICE) == 0 || 160 strcmp(pname, NVME) == 0) && consider_label) { 161 consider_label = B_FALSE; 162 163 if (topo_prop_get_string(pnp, TOPO_PGROUP_PROTOCOL, 164 TOPO_PROP_LABEL, &slotname, &e) == 0) { 165 pp->dp_slotname = slotname; 166 } 167 } 168 169 /* 170 * Next, see if these are nodes where we normally have 171 * facilities. 172 */ 173 if (strcmp(pname, BAY) == 0) { 174 if (topo_node_facility(hp, pnp, TOPO_FAC_TYPE_INDICATOR, 175 TOPO_FAC_TYPE_ANY, &fl, &e) == 0) { 176 for (lp = topo_list_next(&fl.tf_list); 177 lp != NULL; lp = topo_list_next(lp)) { 178 uint32_t prop; 179 180 if (topo_prop_get_uint32(lp->tf_node, 181 TOPO_PGROUP_FACILITY, 182 TOPO_FACILITY_TYPE, &prop, &e) != 183 0) { 184 continue; 185 } 186 type = (topo_led_type_t)prop; 187 188 if (topo_prop_get_uint32(lp->tf_node, 189 TOPO_PGROUP_FACILITY, TOPO_LED_MODE, 190 &prop, &e) != 0) { 191 continue; 192 } 193 mode = (topo_led_state_t)prop; 194 195 switch (type) { 196 case TOPO_LED_TYPE_SERVICE: 197 pp->dp_faulty = mode ? 1 : 0; 198 break; 199 case TOPO_LED_TYPE_LOCATE: 200 pp->dp_locate = mode ? 1 : 0; 201 break; 202 default: 203 break; 204 } 205 } 206 } 207 } 208 209 /* 210 * Finally if this is the chassis node, we want to record its 211 * instance number. 212 */ 213 if (strcmp(pname, CHASSIS) == 0) { 214 pp->dp_chassis = topo_node_instance(pnp); 215 } 216 } 217 218 return (TOPO_WALK_TERMINATE); 219 } 220 221 static void 222 populate_physical(topo_hdl_t *hp, di_phys_t *pp) 223 { 224 int e; 225 topo_walk_t *wp; 226 227 pp->dp_faulty = pp->dp_locate = -1; 228 pp->dp_chassis = pp->dp_slot = -1; 229 230 e = 0; 231 wp = topo_walk_init(hp, FM_FMRI_SCHEME_HC, disk_walker, pp, &e); 232 if (wp == NULL) { 233 errx(-1, "unable to initialise topo walker: %s", 234 topo_strerror(e)); 235 } 236 237 while ((e = topo_walk_step(wp, TOPO_WALK_CHILD)) == TOPO_WALK_NEXT) 238 ; 239 240 if (e == TOPO_WALK_ERR) 241 errx(-1, "topo walk failed"); 242 243 topo_walk_fini(wp); 244 } 245 246 static void 247 enumerate_disks(di_opts_t *opts) 248 { 249 topo_hdl_t *hp = NULL; 250 int filter[] = { DM_DT_FIXED, -1 }; 251 dm_descriptor_t *media; 252 uint_t i; 253 int e; 254 255 e = 0; 256 if ((media = dm_get_descriptors(DM_MEDIA, filter, &e)) == NULL) { 257 errno = e; 258 err(-1, "failed to obtain media descriptors"); 259 } 260 261 /* 262 * We only need to walk topo if we're intending to display 263 * condensed or physical information. If we don't need it, we leave 264 * hp = NULL. 265 */ 266 if (opts->di_condensed || opts->di_physical) { 267 e = 0; 268 hp = topo_open(TOPO_VERSION, NULL, &e); 269 if (hp == NULL) { 270 errx(-1, "unable to obtain topo handle: %s", 271 topo_strerror(e)); 272 } 273 274 e = 0; 275 (void) topo_snap_hold(hp, NULL, &e); 276 if (e != 0) { 277 errx(-1, "unable to hold topo snapshot: %s", 278 topo_strerror(e)); 279 } 280 } 281 282 for (i = 0; media != NULL && media[i] != 0; i++) { 283 dm_descriptor_t *disk, *controller; 284 nvlist_t *mattrs, *dattrs; 285 char *vid, *pid, *serial, *opath, *ctype, *pctype, *c; 286 boolean_t removable, ssd; 287 char device[MAXPATHLEN]; 288 di_phys_t phys; 289 size_t len; 290 uint64_t size, total; 291 uint32_t blocksize; 292 double total_in_GiB; 293 char sizestr[32]; 294 char slotname[32]; 295 char statestr[8]; 296 297 if ((disk = dm_get_associated_descriptors(media[i], 298 DM_DRIVE, &e)) == NULL) { 299 continue; 300 } 301 302 /* 303 * The attributes depend on us being able to get the media 304 * info with DKIOCGMEDIAINFO which may not be the case for 305 * disks which are failing. 306 */ 307 if ((mattrs = dm_get_attributes(media[i], &e)) == NULL) 308 continue; 309 310 e = nvlist_lookup_uint64(mattrs, DM_SIZE, &size); 311 assert(e == 0); 312 e = nvlist_lookup_uint32(mattrs, DM_BLOCKSIZE, &blocksize); 313 assert(e == 0); 314 315 vid = pid = opath = "-"; 316 removable = B_FALSE; 317 ssd = B_FALSE; 318 319 dattrs = dm_get_attributes(disk[0], &e); 320 if (dattrs != NULL) { 321 nvlist_query_string(dattrs, DM_VENDOR_ID, &vid); 322 nvlist_query_string(dattrs, DM_PRODUCT_ID, &pid); 323 nvlist_query_string(dattrs, DM_OPATH, &opath); 324 nvlist_query_string(dattrs, DM_SERIAL, &serial); 325 326 if (nvlist_lookup_boolean(dattrs, DM_REMOVABLE) == 0) 327 removable = B_TRUE; 328 329 if (nvlist_lookup_boolean(dattrs, DM_SOLIDSTATE) == 0) 330 ssd = B_TRUE; 331 } 332 333 pctype = "-"; 334 ctype = NULL; 335 if ((controller = dm_get_associated_descriptors(disk[0], 336 DM_CONTROLLER, &e)) != NULL) { 337 nvlist_t *cattrs; 338 339 cattrs = dm_get_attributes(controller[0], &e); 340 if (cattrs != NULL) { 341 nvlist_query_string(cattrs, DM_CTYPE, &ctype); 342 ctype = strdup(ctype); 343 nvlist_free(cattrs); 344 345 if (ctype != NULL) { 346 for (c = ctype; *c != '\0'; c++) 347 *c = toupper(*c); 348 pctype = ctype; 349 } 350 } 351 dm_free_descriptors(controller); 352 } 353 354 /* 355 * Parse full device path to only show the device name, 356 * i.e. c0t1d0. Many paths will reference a particular 357 * slice (c0t1d0s0), so remove the slice if present. 358 */ 359 if ((c = strrchr(opath, '/')) != NULL) 360 (void) strlcpy(device, c + 1, sizeof (device)); 361 else 362 (void) strlcpy(device, opath, sizeof (device)); 363 len = strlen(device); 364 if (device[len - 2] == 's' && 365 (device[len - 1] >= '0' && device[len - 1] <= '9')) { 366 device[len - 2] = '\0'; 367 } 368 369 if (hp != NULL) { 370 bzero(&phys, sizeof (phys)); 371 phys.dp_dev = device; 372 populate_physical(hp, &phys); 373 374 if (opts->di_parseable) { 375 (void) snprintf(slotname, sizeof (slotname), 376 "%d,%d", phys.dp_chassis, phys.dp_slot); 377 } else if (phys.dp_slotname != NULL && 378 phys.dp_chassis != -1) { 379 (void) snprintf(slotname, sizeof (slotname), 380 "[%d] %s", phys.dp_chassis, 381 phys.dp_slotname); 382 } else if (phys.dp_slotname != NULL) { 383 (void) snprintf(slotname, sizeof (slotname), 384 "%s", phys.dp_slotname); 385 } else { 386 slotname[0] = '-'; 387 slotname[1] = '\0'; 388 } 389 } 390 if (phys.dp_serial == NULL) { 391 phys.dp_serial = serial; 392 } 393 394 /* 395 * The size is given in blocks, so multiply the number 396 * of blocks by the block size to get the total size, 397 * then convert to GiB. 398 */ 399 total = size * blocksize; 400 401 if (opts->di_parseable) { 402 (void) snprintf(sizestr, sizeof (sizestr), 403 "%llu", total); 404 } else { 405 total_in_GiB = (double)total / 406 1024.0 / 1024.0 / 1024.0; 407 (void) snprintf(sizestr, sizeof (sizestr), 408 "%7.2f GiB", total_in_GiB); 409 } 410 411 if (opts->di_condensed) { 412 (void) snprintf(statestr, sizeof (statestr), "%c%c%c%c", 413 condensed_tristate(phys.dp_faulty, 'F'), 414 condensed_tristate(phys.dp_locate, 'L'), 415 condensed_tristate(removable, 'R'), 416 condensed_tristate(ssd, 'S')); 417 } 418 419 if (opts->di_physical) { 420 if (opts->di_scripted) { 421 printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 422 device, vid, pid, 423 display_string(phys.dp_serial), 424 display_tristate(phys.dp_faulty), 425 display_tristate(phys.dp_locate), slotname); 426 } else { 427 printf("%-22s %-8s %-16s " 428 "%-20s %-3s %-3s %s\n", 429 device, vid, pid, 430 display_string(phys.dp_serial), 431 display_tristate(phys.dp_faulty), 432 display_tristate(phys.dp_locate), slotname); 433 } 434 } else if (opts->di_condensed) { 435 if (opts->di_scripted) { 436 printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 437 pctype, device, vid, pid, 438 display_string(phys.dp_serial), 439 sizestr, statestr, slotname); 440 } else { 441 printf("%-7s %-22s %-8s %-16s " 442 "%-20s\n\t%-13s %-4s %s\n", 443 pctype, device, vid, pid, 444 display_string(phys.dp_serial), 445 sizestr, statestr, slotname); 446 } 447 } else { 448 if (opts->di_scripted) { 449 printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 450 pctype, device, vid, pid, sizestr, 451 display_tristate(removable), 452 display_tristate(ssd)); 453 } else { 454 printf("%-7s %-22s %-8s %-16s " 455 "%-13s %-3s %-3s\n", pctype, device, 456 vid, pid, sizestr, 457 display_tristate(removable), 458 display_tristate(ssd)); 459 } 460 } 461 462 free(ctype); 463 nvlist_free(dattrs); 464 nvlist_free(mattrs); 465 dm_free_descriptors(disk); 466 } 467 468 dm_free_descriptors(media); 469 if (hp != NULL) { 470 topo_snap_release(hp); 471 topo_close(hp); 472 } 473 } 474 475 int 476 main(int argc, char *argv[]) 477 { 478 int c; 479 480 di_opts_t opts = { 481 .di_condensed = B_FALSE, 482 .di_scripted = B_FALSE, 483 .di_physical = B_FALSE, 484 .di_parseable = B_FALSE 485 }; 486 487 while ((c = getopt(argc, argv, ":cHPp")) != EOF) { 488 switch (c) { 489 case 'c': 490 if (opts.di_physical) { 491 usage(argv[0]); 492 errx(1, "-c and -P are mutually exclusive"); 493 } 494 opts.di_condensed = B_TRUE; 495 break; 496 case 'H': 497 opts.di_scripted = B_TRUE; 498 break; 499 case 'P': 500 if (opts.di_condensed) { 501 usage(argv[0]); 502 errx(1, "-c and -P are mutually exclusive"); 503 } 504 opts.di_physical = B_TRUE; 505 break; 506 case 'p': 507 opts.di_parseable = B_TRUE; 508 break; 509 case '?': 510 usage(argv[0]); 511 errx(1, "unknown option -%c", optopt); 512 default: 513 errx(-1, "unexpected error on option -%c", optopt); 514 } 515 } 516 517 if (!opts.di_scripted) { 518 if (opts.di_physical) { 519 printf("DISK VID PID" 520 " SERIAL FLT LOC" 521 " LOCATION\n"); 522 } else if (opts.di_condensed) { 523 printf("TYPE DISK VID PID" 524 " SERIAL\n"); 525 printf("\tSIZE FLRS LOCATION\n"); 526 } else { 527 printf("TYPE DISK VID PID" 528 " SIZE RMV SSD\n"); 529 } 530 } 531 532 enumerate_disks(&opts); 533 534 return (0); 535 } 536