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