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 2022 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 tnode_t *pnp; 106 tnode_t *ppnp; 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 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 pnp = topo_node_parent(np); 131 ppnp = topo_node_parent(pnp); 132 pp->dp_chassis = topo_node_instance(ppnp); 133 if (strcmp(topo_node_name(pnp), BAY) == 0) { 134 if (topo_node_facility(hp, pnp, TOPO_FAC_TYPE_INDICATOR, 135 TOPO_FAC_TYPE_ANY, &fl, &e) == 0) { 136 for (lp = topo_list_next(&fl.tf_list); lp != NULL; 137 lp = topo_list_next(lp)) { 138 uint32_t prop; 139 140 if (topo_prop_get_uint32(lp->tf_node, 141 TOPO_PGROUP_FACILITY, TOPO_FACILITY_TYPE, 142 &prop, &e) != 0) { 143 continue; 144 } 145 type = (topo_led_type_t)prop; 146 147 if (topo_prop_get_uint32(lp->tf_node, 148 TOPO_PGROUP_FACILITY, TOPO_LED_MODE, 149 &prop, &e) != 0) { 150 continue; 151 } 152 mode = (topo_led_state_t)prop; 153 154 switch (type) { 155 case TOPO_LED_TYPE_SERVICE: 156 pp->dp_faulty = mode ? 1 : 0; 157 break; 158 case TOPO_LED_TYPE_LOCATE: 159 pp->dp_locate = mode ? 1 : 0; 160 break; 161 default: 162 break; 163 } 164 } 165 } 166 167 if (topo_prop_get_string(pnp, TOPO_PGROUP_PROTOCOL, 168 TOPO_PROP_LABEL, &slotname, &e) == 0) { 169 pp->dp_slotname = slotname; 170 } 171 172 pp->dp_slot = topo_node_instance(pnp); 173 } else if (strcmp(topo_node_name(pnp), USB_DEVICE) == 0) { 174 if (topo_prop_get_string(pnp, TOPO_PGROUP_PROTOCOL, 175 TOPO_PROP_LABEL, &slotname, &e) == 0) { 176 pp->dp_slotname = slotname; 177 } 178 179 /* 180 * Override dp_chassis for USB devices since they show up 181 * everywhere in the name space and may not be under a logical 182 * bay. 183 */ 184 pp->dp_chassis = -1; 185 } 186 187 return (TOPO_WALK_TERMINATE); 188 } 189 190 static void 191 populate_physical(topo_hdl_t *hp, di_phys_t *pp) 192 { 193 int e; 194 topo_walk_t *wp; 195 196 pp->dp_faulty = pp->dp_locate = -1; 197 pp->dp_chassis = pp->dp_slot = -1; 198 199 e = 0; 200 wp = topo_walk_init(hp, FM_FMRI_SCHEME_HC, disk_walker, pp, &e); 201 if (wp == NULL) { 202 errx(-1, "unable to initialise topo walker: %s", 203 topo_strerror(e)); 204 } 205 206 while ((e = topo_walk_step(wp, TOPO_WALK_CHILD)) == TOPO_WALK_NEXT) 207 ; 208 209 if (e == TOPO_WALK_ERR) 210 errx(-1, "topo walk failed"); 211 212 topo_walk_fini(wp); 213 } 214 215 static void 216 enumerate_disks(di_opts_t *opts) 217 { 218 topo_hdl_t *hp = NULL; 219 int filter[] = { DM_DT_FIXED, -1 }; 220 dm_descriptor_t *media; 221 uint_t i; 222 int e; 223 224 e = 0; 225 if ((media = dm_get_descriptors(DM_MEDIA, filter, &e)) == NULL) { 226 errno = e; 227 err(-1, "failed to obtain media descriptors"); 228 } 229 230 /* 231 * We only need to walk topo if we're intending to display 232 * condensed or physical information. If we don't need it, we leave 233 * hp = NULL. 234 */ 235 if (opts->di_condensed || opts->di_physical) { 236 e = 0; 237 hp = topo_open(TOPO_VERSION, NULL, &e); 238 if (hp == NULL) { 239 errx(-1, "unable to obtain topo handle: %s", 240 topo_strerror(e)); 241 } 242 243 e = 0; 244 (void) topo_snap_hold(hp, NULL, &e); 245 if (e != 0) { 246 errx(-1, "unable to hold topo snapshot: %s", 247 topo_strerror(e)); 248 } 249 } 250 251 for (i = 0; media != NULL && media[i] != 0; i++) { 252 dm_descriptor_t *disk, *controller; 253 nvlist_t *mattrs, *dattrs; 254 char *vid, *pid, *opath, *ctype, *pctype, *c; 255 boolean_t removable, ssd; 256 char device[MAXPATHLEN]; 257 di_phys_t phys; 258 size_t len; 259 uint64_t size, total; 260 uint32_t blocksize; 261 double total_in_GiB; 262 char sizestr[32]; 263 char slotname[32]; 264 char statestr[8]; 265 266 if ((disk = dm_get_associated_descriptors(media[i], 267 DM_DRIVE, &e)) == NULL) { 268 continue; 269 } 270 271 /* 272 * The attributes depend on us being able to get the media 273 * info with DKIOCGMEDIAINFO which may not be the case for 274 * disks which are failing. 275 */ 276 if ((mattrs = dm_get_attributes(media[i], &e)) == NULL) 277 continue; 278 279 e = nvlist_lookup_uint64(mattrs, DM_SIZE, &size); 280 assert(e == 0); 281 e = nvlist_lookup_uint32(mattrs, DM_BLOCKSIZE, &blocksize); 282 assert(e == 0); 283 284 vid = pid = opath = "-"; 285 removable = B_FALSE; 286 ssd = B_FALSE; 287 288 dattrs = dm_get_attributes(disk[0], &e); 289 if (dattrs != NULL) { 290 nvlist_query_string(dattrs, DM_VENDOR_ID, &vid); 291 nvlist_query_string(dattrs, DM_PRODUCT_ID, &pid); 292 nvlist_query_string(dattrs, DM_OPATH, &opath); 293 294 if (nvlist_lookup_boolean(dattrs, DM_REMOVABLE) == 0) 295 removable = B_TRUE; 296 297 if (nvlist_lookup_boolean(dattrs, DM_SOLIDSTATE) == 0) 298 ssd = B_TRUE; 299 } 300 301 pctype = "-"; 302 ctype = NULL; 303 if ((controller = dm_get_associated_descriptors(disk[0], 304 DM_CONTROLLER, &e)) != NULL) { 305 nvlist_t *cattrs; 306 307 cattrs = dm_get_attributes(controller[0], &e); 308 if (cattrs != NULL) { 309 nvlist_query_string(cattrs, DM_CTYPE, &ctype); 310 ctype = strdup(ctype); 311 nvlist_free(cattrs); 312 313 if (ctype != NULL) { 314 for (c = ctype; *c != '\0'; c++) 315 *c = toupper(*c); 316 pctype = ctype; 317 } 318 } 319 dm_free_descriptors(controller); 320 } 321 322 /* 323 * Parse full device path to only show the device name, 324 * i.e. c0t1d0. Many paths will reference a particular 325 * slice (c0t1d0s0), so remove the slice if present. 326 */ 327 if ((c = strrchr(opath, '/')) != NULL) 328 (void) strlcpy(device, c + 1, sizeof (device)); 329 else 330 (void) strlcpy(device, opath, sizeof (device)); 331 len = strlen(device); 332 if (device[len - 2] == 's' && 333 (device[len - 1] >= '0' && device[len - 1] <= '9')) { 334 device[len - 2] = '\0'; 335 } 336 337 if (hp != NULL) { 338 bzero(&phys, sizeof (phys)); 339 phys.dp_dev = device; 340 populate_physical(hp, &phys); 341 342 if (opts->di_parseable) { 343 (void) snprintf(slotname, sizeof (slotname), 344 "%d,%d", phys.dp_chassis, phys.dp_slot); 345 } else if (phys.dp_slotname != NULL && 346 phys.dp_chassis != -1) { 347 (void) snprintf(slotname, sizeof (slotname), 348 "[%d] %s", phys.dp_chassis, 349 phys.dp_slotname); 350 } else if (phys.dp_slotname != NULL) { 351 (void) snprintf(slotname, sizeof (slotname), 352 "%s", phys.dp_slotname); 353 } else { 354 slotname[0] = '-'; 355 slotname[1] = '\0'; 356 } 357 } 358 359 /* 360 * The size is given in blocks, so multiply the number 361 * of blocks by the block size to get the total size, 362 * then convert to GiB. 363 */ 364 total = size * blocksize; 365 366 if (opts->di_parseable) { 367 (void) snprintf(sizestr, sizeof (sizestr), 368 "%llu", total); 369 } else { 370 total_in_GiB = (double)total / 371 1024.0 / 1024.0 / 1024.0; 372 (void) snprintf(sizestr, sizeof (sizestr), 373 "%7.2f GiB", total_in_GiB); 374 } 375 376 if (opts->di_condensed) { 377 (void) snprintf(statestr, sizeof (statestr), "%c%c%c%c", 378 condensed_tristate(phys.dp_faulty, 'F'), 379 condensed_tristate(phys.dp_locate, 'L'), 380 condensed_tristate(removable, 'R'), 381 condensed_tristate(ssd, 'S')); 382 } 383 384 if (opts->di_physical) { 385 if (opts->di_scripted) { 386 printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 387 device, vid, pid, 388 display_string(phys.dp_serial), 389 display_tristate(phys.dp_faulty), 390 display_tristate(phys.dp_locate), slotname); 391 } else { 392 printf("%-22s %-8s %-16s " 393 "%-20s %-3s %-3s %s\n", 394 device, vid, pid, 395 display_string(phys.dp_serial), 396 display_tristate(phys.dp_faulty), 397 display_tristate(phys.dp_locate), slotname); 398 } 399 } else if (opts->di_condensed) { 400 if (opts->di_scripted) { 401 printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 402 pctype, device, vid, pid, 403 display_string(phys.dp_serial), 404 sizestr, statestr, slotname); 405 } else { 406 printf("%-7s %-22s %-8s %-16s " 407 "%-20s\n\t%-13s %-4s %s\n", 408 pctype, device, vid, pid, 409 display_string(phys.dp_serial), 410 sizestr, statestr, slotname); 411 } 412 } else { 413 if (opts->di_scripted) { 414 printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 415 pctype, device, vid, pid, sizestr, 416 display_tristate(removable), 417 display_tristate(ssd)); 418 } else { 419 printf("%-7s %-22s %-8s %-16s " 420 "%-13s %-3s %-3s\n", pctype, device, 421 vid, pid, sizestr, 422 display_tristate(removable), 423 display_tristate(ssd)); 424 } 425 } 426 427 free(ctype); 428 nvlist_free(dattrs); 429 nvlist_free(mattrs); 430 dm_free_descriptors(disk); 431 } 432 433 dm_free_descriptors(media); 434 if (hp != NULL) { 435 topo_snap_release(hp); 436 topo_close(hp); 437 } 438 } 439 440 int 441 main(int argc, char *argv[]) 442 { 443 char c; 444 445 di_opts_t opts = { 446 .di_condensed = B_FALSE, 447 .di_scripted = B_FALSE, 448 .di_physical = B_FALSE, 449 .di_parseable = B_FALSE 450 }; 451 452 while ((c = getopt(argc, argv, ":cHPp")) != EOF) { 453 switch (c) { 454 case 'c': 455 if (opts.di_physical) { 456 usage(argv[0]); 457 errx(1, "-c and -P are mutually exclusive"); 458 } 459 opts.di_condensed = B_TRUE; 460 break; 461 case 'H': 462 opts.di_scripted = B_TRUE; 463 break; 464 case 'P': 465 if (opts.di_condensed) { 466 usage(argv[0]); 467 errx(1, "-c and -P are mutually exclusive"); 468 } 469 opts.di_physical = B_TRUE; 470 break; 471 case 'p': 472 opts.di_parseable = B_TRUE; 473 break; 474 case '?': 475 usage(argv[0]); 476 errx(1, "unknown option -%c", optopt); 477 default: 478 errx(-1, "unexpected error on option -%c", optopt); 479 } 480 } 481 482 if (!opts.di_scripted) { 483 if (opts.di_physical) { 484 printf("DISK VID PID" 485 " SERIAL FLT LOC" 486 " LOCATION\n"); 487 } else if (opts.di_condensed) { 488 printf("TYPE DISK VID PID" 489 " SERIAL\n"); 490 printf("\tSIZE FLRS LOCATION\n"); 491 } else { 492 printf("TYPE DISK VID PID" 493 " SIZE RMV SSD\n"); 494 } 495 } 496 497 enumerate_disks(&opts); 498 499 return (0); 500 } 501