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