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